linux-perf-users.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics
@ 2026-11-13 19:53 Ian Rogers
  2026-11-13 19:53 ` [PATCH v2 01/30] perf pmus: Don't skip core PMUs as duplicates Ian Rogers
                   ` (29 more replies)
  0 siblings, 30 replies; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

This series fixes some PMU and metric bugs, refactors PMU state
management into struct perf_env, makes 'perf stat' share metric events
by what they count, and computes and displays metrics from recorded
perf.data files in 'perf stat report' and 'perf script'.

It is the first part of what was sent as a single 41 patch v1 series
"perf: Per-env PMUs, recorded event metrics, and perf sysinfo":
https://lore.kernel.org/all/20261103234502.1008382-1-irogers@google.com/
The 'perf sysinfo' subcommand is sent as a second series that depends on
this one.

The series is ordered as: independent fixes (Patches 1-4), clean ups
(Patches 5-22), 'perf stat' metric event sharing (Patches 23-24) and
perf.data metric support (Patches 25-30).

1. Independent fixes (Patches 1-4)
==================================

These fix existing bugs and apply independently of the rest of the
series:

  - Patch 1 stops 'perf list' treating core PMUs whose names differ by
    a suffix, such as armv8_pmuv3_0 and armv8_pmuv3_1, as duplicates
    and so hiding the events of all but the first core PMU.
  - Patch 2 fixes 'perf script -F +metric' sample-based metric
    calculation, the counts of other CPUs were left as deltas giving
    wrong (negative) values, metrics of unrelated events were shown and
    the output was garbled.
  - Patch 3 stops 'perf script -F metric' dropping samples whose CPU
    isn't in the evsel's CPU map, which for perf record data is the
    host's online CPUs.
  - Patch 4 stops 'perf stat' displaying a metric alongside a tool
    event, such as duration_time, when a non-tool event is available.

2. Clean ups (Patches 5-22)
===========================

Previously, core_pmus, other_pmus, and sysfs scanning state were global
variables in tools/perf/util/pmus.c, preventing perf from maintaining
both the live host's PMUs and a recorded perf.data session's PMUs
simultaneously.

   - Patch 5 makes lazy perf_env initialization thread-safe using
     env->env_sem.
   - Patch 6 introduces the host_env() singleton to represent the live
     host environment explicitly, replacing the global perf_env variable
     and session->header.env fallbacks.
   - Patch 7 adds a from_env parameter to perf_pmu__init() to
     distinguish PMUs reconstructed from perf_env headers from live host
     PMUs so they never query host sysfs or get cast via container_of()
     to struct hwmon_pmu or struct drm_pmu.
   - Patches 8-10 introduce struct perf_pmus embedded in struct perf_env
     (accessed via perf_env__pmus()) and pass struct perf_pmus to the
     PMU find and scan helpers.
   - Patches 11-13 record the ELF machine (e_machine) of each pmu-events
     table's CPUIDs in struct pmu_events_map, so host lookups only
     consider the host architecture's tables, find the core events and
     metrics tables by e_machine rather than an architecture name, and
     pass the e_machine of the CPUIDs to strcmp_cpuid_str().
   - Patch 14 moves the architecture-specific strcmp_cpuid_str()
     implementations from arch/{x86,arm64}/util/header.c into common
     tools/perf/util/header.c, selecting the comparison by e_machine, so
     CPUID matching works across architectures when analyzing cross-arch
     perf.data files.
   - Patches 15-16 move the arm64 CPUID matching test to
     tools/perf/tests so it runs on all architectures, and add CPUID
     matching tests for exact (test/common), x86, PowerPC, RISC-V and
     s390 CPUIDs.
   - Patch 17 adds e_machine__same_arch() to compare ELF machines
     ignoring word size differences, such as EM_386 and EM_X86_64.
   - Patch 18 passes struct perf_pmus to metricgroup__for_each_metric()
     and pmu_metrics_table__find().
   - Patch 19 moves the matching of metrics to recorded events
     (metricgroup__add_matching_metrics()) from builtin-script.c into
     util/metricgroup.c.
   - Patch 20 moves the encoding and decoding of metric-id term values
     into parse-events.c so the metric-id is decoded when the evsel is
     created, rather than metricgroup rewriting each evsel's metric-id
     after parsing.
   - Patch 21 generalizes evsel::metric_id to a set of unique
     evsel::metric_ids, so that an evsel shared by metrics, or matched
     by metrics to a recorded event, has the identifiers of all of them.
     The set's representation is private to evsel.c and accessed through
     evsel__add_metric_id(), evsel__copy_metric_ids(),
     evsel__has_metric_id(), evsel__shares_metric_id() and the
     evsel__for_each_metric_id() callback iterator.
   - Patch 22 adds perf_pmu__for_each_name_from_config() to find the event
     names of a PMU for an encoding, including legacy hardware and cache
     event names from the legacy event tables, and uses it for
     perf_pmu__name_from_config().

3. 'perf stat' metric event sharing (Patches 23-24)
===================================================

Metrics shared events by the names of the identifiers in their
expressions, so events spelled differently but counting the same thing
weren't shared, and events were only shared within one metric group
parsing call.

   - Patch 23 adds an evsel matcher comparing a key of the PMU, the
     perf_event_attr encoding (with legacy hardware events converted to
     the equivalent sysfs/json encoding), modifiers and cgroup, and uses
     it to share events between metrics, earlier metric groups and the
     command line without splitting a metric's events across different
     groups, while preserving multi-PMU metric leadership and merging
     forced architecture groups such as x86 topdown events.
   - Patch 24 makes the -T, --smi-cost, --topdown and Default metrics
     share events with the command line events and each other.

4. perf.data metric support (Patches 25-30)
===========================================

With per-perf_env PMUs reconstructed from perf.data headers, perf can
resolve the recorded machine's PMUs and JSON metric tables offline and
match metrics against recorded evsels.

   - Patch 25 adds perf_pmus__read_from_env() and perf_pmu__add_cap() to
     reconstruct a perf_env's core_pmus and other_pmus lists,
     capabilities, CPU maps, and JSON event/metric tables from perf.data
     headers.
   - Patch 26 improves metricgroup__add_matching_metrics() to match
     each candidate metric's events to recorded evlist entries with the
     evsel matcher, in a mode tolerating evsel__fallback() exclusion
     changes and preferring events with counts, attaching copies of the
     metric to each PMU's events, supporting uncore PMU instance fan-out
     and hybrid PMUs.
   - Patch 27 adds metricgroup__add_recorded_metrics() and
     metricgroup__add_matching_metric_groups() to reconstruct the
     metrics requested on a recorded 'perf stat record' command line
     from perf_env (including -M, --transaction, --smi-cost, --topdown,
     -d, or the Default metricgroups when no events or metrics were
     specified), or optionally add all matching JSON metrics when
     --all-metrics is enabled.
   - Patch 28 enables displaying metrics for recorded events in
     'perf stat report' (and adds --all-metrics and stat.all-metrics).
   - Patch 29 enables displaying metrics for 'perf stat record'
     PERF_RECORD_STAT data in 'perf script' (and adds --all-metrics).
   - Patch 30 adds a shell test verifying metric reporting across
     'perf stat record', 'perf stat report', and 'perf script'.

Changes since v1
================
- Split the series into two, this series and a 'perf sysinfo' series.
- Reordered the series as independent fixes, clean ups, 'perf stat'
  metric event sharing and perf.data metric support, so the fixes
  (Patches 1-4) can be applied independently.
- New patch 1: don't skip core PMUs as duplicates, affecting both
  'perf list' and 'perf sysinfo'.
- New patch 4: don't prefer tool events for displaying a metric.
- New patch 17: add e_machine__same_arch() so that comparing a
  perf_env's architecture with the host's ignores word size differences
  (EM_386 vs EM_X86_64) without string comparisons, and use it for
  pmu-events table matching, is_pmu_core_len() and env_is_pmu_core().
- New patches 23-24: share metric events in 'perf stat' using the same
  evsel matcher as used for matching metrics to perf.data events, so
  there is one implementation of deciding whether two events count the
  same thing (Patch 26 now builds on it). Previously metrics shared
  events by the names in their expressions.
- Rebased onto the latest perf-tools-next.
- Addressed Sashiko review feedback:
  - Restored evsel__is_drm()/evsel__is_hwmon() and made all drm/hwmon
    open and read callbacks fail with -EINVAL for PMUs from a perf_env.
  - Fixed config_terms leaks in parse_events_add_pmu() and
    parse_events_add_breakpoint().
  - Handled HEADER_E_MACHINE in perf_event__process_feature(), and
    cleared is_uncore when upgrading or promoting env PMUs.
  - Metric matching: don't count a recorded event twice when different
    metric events match it, map all events before modifying recorded
    evsels, only treat dynamic PMU types as raw when the PMU name is
    known, only prefer matching grouping when the metric's events are
    grouped, tolerate EOPNOTSUPP exclude_guest fallback, and on hybrid
    systems add metrics for each PMU whose events were recorded.
  - Only mark recorded events used by the Default metrics, including
    duplicates across groups, as in the Default metricgroup.
  - Handle an empty thread map when determining system wide mode in
    'perf stat report' and 'perf script'.
  - Clean up all temporary files in the stat record metrics test.

This patch series was generated with the assistance of antigravity and
Gemini 4.0.

Ian Rogers (30):
  perf pmus: Don't skip core PMUs as duplicates
  perf script: Fix metrics computed from samples
  perf script: Don't drop metric samples for CPUs missing from the evsel
  perf metricgroup: Don't prefer tool events for displaying a metric
  perf env: Make lazy perf_env initialization thread-safe
  perf env: Add host_env() singleton
  perf pmu: Add from_env parameter to perf_pmu__init()
  perf pmus: Introduce struct perf_pmus and perf_env__pmus()
  perf pmus: Pass struct perf_pmus to find helpers
  perf pmus: Pass struct perf_pmus to scan helpers
  perf pmu-events: Add e_machine to struct pmu_events_map
  perf pmu-events: Pass e_machine to find_core_*_table()
  perf header: Pass e_machine to strcmp_cpuid_str()
  perf header: Support CPUID matching across all architectures
  perf test: Make the CPUID matching test architecture independent
  perf test: Add CPUID matching tests for other architectures
  perf dwarf-regs: Add e_machine__same_arch()
  perf metricgroup: Pass struct perf_pmus to
    metricgroup__for_each_metric()
  perf metricgroup: Move matching of metrics to recorded events from
    perf script
  perf parse-events: Decode metric-id terms when adding events
  perf evsel: Generalize metric_id to a set of metric_ids
  perf pmu: Add perf_pmu__for_each_name_from_config()
  perf metricgroup: Share metric events by their counter encoding
  perf stat: Share events between metrics and command line events
  perf pmus: Add perf_pmus__read_from_env()
  perf metricgroup: Improve matching of metrics to recorded events
  perf metricgroup: Add metricgroup__add_recorded_metrics()
  perf stat report: Display metrics for recorded events
  perf script: Display metrics for perf stat record data
  perf test: Add stat record metrics test

 tools/perf/Documentation/perf-config.txt      |    7 +
 tools/perf/Documentation/perf-script.txt      |    6 +
 tools/perf/Documentation/perf-stat.txt        |    5 +
 tools/perf/arch/arm/util/auxtrace.c           |    9 +-
 tools/perf/arch/arm/util/cs-etm.c             |    3 +-
 tools/perf/arch/arm64/include/arch-tests.h    |    1 -
 tools/perf/arch/arm64/tests/Build             |    1 -
 tools/perf/arch/arm64/tests/arch-tests.c      |    4 -
 tools/perf/arch/arm64/tests/cpuid-match.c     |   37 -
 tools/perf/arch/arm64/util/header.c           |   48 -
 tools/perf/arch/arm64/util/pmu.c              |    3 +-
 tools/perf/arch/x86/tests/amd-ibs-period.c    |    5 +-
 .../arch/x86/tests/amd-ibs-via-core-pmu.c     |    3 +-
 tools/perf/arch/x86/tests/hybrid.c            |    6 +-
 tools/perf/arch/x86/tests/topdown.c           |    6 +-
 tools/perf/arch/x86/util/auxtrace.c           |    9 +-
 tools/perf/arch/x86/util/evsel.c              |    6 +-
 tools/perf/arch/x86/util/header.c             |   64 -
 tools/perf/arch/x86/util/intel-bts.c          |    4 +-
 tools/perf/arch/x86/util/intel-pt.c           |    4 +-
 tools/perf/arch/x86/util/pmu.c                |   10 +-
 tools/perf/arch/x86/util/topdown.c            |    3 +-
 tools/perf/bench/pmu-scan.c                   |   16 +-
 tools/perf/bench/synthesize.c                 |   13 +-
 tools/perf/builtin-buildid-list.c             |    5 +-
 tools/perf/builtin-inject.c                   |    3 +-
 tools/perf/builtin-kallsyms.c                 |   12 +-
 tools/perf/builtin-list.c                     |   19 +-
 tools/perf/builtin-lock.c                     |    6 +-
 tools/perf/builtin-record.c                   |    2 +-
 tools/perf/builtin-script.c                   |  310 +--
 tools/perf/builtin-stat.c                     |  136 +-
 tools/perf/builtin-top.c                      |   17 +-
 tools/perf/builtin-trace.c                    |   18 +-
 tools/perf/perf.c                             |    3 +
 tools/perf/pmu-events/empty-pmu-events.c      |   30 +-
 tools/perf/pmu-events/jevents.py              |   41 +-
 tools/perf/pmu-events/pmu-events.h            |    9 +-
 tools/perf/tests/Build                        |    1 +
 tools/perf/tests/builtin-test.c               |    1 +
 tools/perf/tests/code-reading.c               |    5 +-
 tools/perf/tests/cpuid-match.c                |  126 +
 tools/perf/tests/dlfilter-test.c              |    5 +-
 tools/perf/tests/dwarf-unwind.c               |    5 +-
 tools/perf/tests/event_groups.c               |    4 +-
 tools/perf/tests/expand-cgroup.c              |    3 +-
 tools/perf/tests/hwmon_pmu.c                  |    4 +-
 tools/perf/tests/maps.c                       |   11 +-
 tools/perf/tests/mmap-basic.c                 |    6 +-
 tools/perf/tests/mmap-thread-lookup.c         |    5 +-
 tools/perf/tests/parse-events.c               |   38 +-
 tools/perf/tests/parse-metric.c               |    3 +-
 tools/perf/tests/pmu-events.c                 |   15 +-
 tools/perf/tests/pmu.c                        |    3 +-
 tools/perf/tests/shell/stat.sh                |  133 +
 tools/perf/tests/symbols.c                    |    7 +-
 tools/perf/tests/tests.h                      |    1 +
 tools/perf/tests/uncore-event-sorting.c       |    6 +-
 tools/perf/trace/beauty/perf_event_open.c     |    4 +-
 tools/perf/util/aslr.c                        |    7 +-
 tools/perf/util/config.c                      |    3 +
 tools/perf/util/cputopo.c                     |    5 +-
 tools/perf/util/debug.c                       |    6 +-
 tools/perf/util/drm_pmu.c                     |   16 +-
 tools/perf/util/env.c                         |  260 +-
 tools/perf/util/env.h                         |   25 +
 tools/perf/util/evlist.c                      |   16 +-
 tools/perf/util/evlist.h                      |    2 +
 tools/perf/util/evsel.c                       |  192 +-
 tools/perf/util/evsel.h                       |   10 +-
 tools/perf/util/evsel_fprintf.c               |    6 +-
 tools/perf/util/evsel_fprintf.h               |    4 +-
 tools/perf/util/expr.c                        |   18 +-
 tools/perf/util/expr.h                        |    2 -
 tools/perf/util/header.c                      |  204 +-
 tools/perf/util/header.h                      |    6 +-
 tools/perf/util/hwmon_pmu.c                   |   16 +-
 tools/perf/util/include/dwarf-regs.h          |   37 +
 .../util/kvm-stat-arch/kvm-stat-powerpc.c     |    3 +-
 tools/perf/util/machine.c                     |   16 +-
 tools/perf/util/machine.h                     |   28 +-
 tools/perf/util/mem-events.c                  |    5 +-
 tools/perf/util/metricgroup.c                 | 2148 ++++++++++++++---
 tools/perf/util/metricgroup.h                 |   95 +-
 tools/perf/util/parse-events.c                |  185 +-
 tools/perf/util/parse-events.h                |    4 +
 .../perf/util/perf-regs-arch/perf_regs_x86.c  |   11 +-
 tools/perf/util/perf_api_probe.c              |    3 +-
 tools/perf/util/perf_event_attr_fprintf.c     |    7 +-
 tools/perf/util/pfm.c                         |    6 +-
 tools/perf/util/pmu.c                         |  195 +-
 tools/perf/util/pmu.h                         |   28 +-
 tools/perf/util/pmus.c                        |  573 +++--
 tools/perf/util/pmus.h                        |  264 +-
 tools/perf/util/print-events.c                |   10 +-
 tools/perf/util/probe-event.c                 |    5 +-
 tools/perf/util/python.c                      |   38 +-
 tools/perf/util/rwsem.h                       |    6 +
 tools/perf/util/s390-sample-raw.c             |   16 +-
 tools/perf/util/session.c                     |   32 +-
 tools/perf/util/session.h                     |   22 +-
 tools/perf/util/stat-display.c                |    4 +-
 tools/perf/util/stat-shadow.c                 |   40 +-
 tools/perf/util/stat.h                        |    1 +
 tools/perf/util/tool_pmu.c                    |    5 +-
 105 files changed, 4538 insertions(+), 1317 deletions(-)
 delete mode 100644 tools/perf/arch/arm64/tests/cpuid-match.c
 create mode 100644 tools/perf/tests/cpuid-match.c


base-commit: 4af65363b11fc4fe65fcb497f68bbe4f3e3a4d2c
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply	[flat|nested] 61+ messages in thread

* [PATCH v2 01/30] perf pmus: Don't skip core PMUs as duplicates
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:04   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 02/30] perf script: Fix metrics computed from samples Ian Rogers
                   ` (28 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

perf_pmus__scan_skip_duplicates() skips a PMU whose name, without a
numeric suffix, matches the previous PMU's. This is intended for uncore
PMUs, such as uncore_cha_0 and uncore_cha_1, that have the same events.
The check was also applied to core PMUs, but core PMUs of heterogeneous
systems may differ only by such a suffix while having different events.
For example, ARM ACPI systems with two kinds of core name their PMUs
armv8_pmuv3_0 and armv8_pmuv3_1, and devicetree systems may name them
armv8_cortex_a53 and armv8_cortex_a57 where "a53" and "a57" are hex
suffixes. Only the first core PMU was listed by "perf list" and the
events of the other core PMUs were missing.

Before, on such a system, "perf list" and "perf list --unit=<pmu>" only
showed the events of the first core PMU, the scan having skipped the
others. After, the events of every core PMU are shown, as on Intel
hybrid systems with cpu_core and cpu_atom PMUs. Uncore PMU deduplication
is unchanged and pmu_deduped_name_len() already avoided removing the
suffix of core PMU names when printing events. Similarly avoid removing
the suffix of core PMU names in the raw event encodings that "perf list"
shows for each PMU, so that each core PMU's encoding is shown with its
own name.

Fixes: cd4e1efbbc40 ("perf pmus: Skip duplicate PMUs and don't print list suffix by default")
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmus.c | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
index 1355b317f3ed..4f4632f48e16 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -465,15 +465,14 @@ static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu)
 		last_pmu_name_len = pmu_name_len_no_suffix(pmu->name ?: "");
 
 	if (use_core_pmus) {
-		list_for_each_entry_continue(pmu, &core_pmus, list) {
-			int pmu_name_len = pmu_name_len_no_suffix(pmu->name ?: "");
-
-			if (last_pmu_name_len == pmu_name_len &&
-			    !strncmp(last_pmu_name, pmu->name ?: "", pmu_name_len))
-				continue;
-
+		/*
+		 * Core PMUs, such as armv8_cortex_a53 and armv8_cortex_a57,
+		 * may differ only by a suffix but have different events, so
+		 * they are never duplicates.
+		 */
+		list_for_each_entry_continue(pmu, &core_pmus, list)
 			return pmu;
-		}
+
 		pmu = NULL;
 		pmu = list_prepare_entry(pmu, &other_pmus, list);
 	}
@@ -762,7 +761,9 @@ void perf_pmus__print_raw_pmu_events(const struct print_callbacks *print_cb, voi
 			.long_string = STRBUF_INIT,
 			.num_formats = 0,
 		};
-		int len = pmu_name_len_no_suffix(pmu->name);
+		/* Core PMUs aren't duplicates so keep their suffix. */
+		int len = pmu->is_core ? (int)strlen(pmu->name)
+				       : (int)pmu_name_len_no_suffix(pmu->name);
 		const char *desc = "(see 'man perf-list' or 'man perf-record' on how to encode it)";
 
 		if (!pmu->is_core)
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 02/30] perf script: Fix metrics computed from samples
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
  2026-11-13 19:53 ` [PATCH v2 01/30] perf pmus: Don't skip core PMUs as duplicates Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:05   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 03/30] perf script: Don't drop metric samples for CPUs missing from the evsel Ian Rogers
                   ` (27 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

perf script -F metric computes metrics from the sum of sample periods of
the events, but the computed values were wrong and the output was
garbled:

 - perf_stat_process_counter() replaces each count with its delta from
   the previous raw count. As counts were left as deltas, the next sample
   computed negative deltas for the counts of all CPUs and threads other
   than the sample's. Restore the counts to the running totals after
   processing.

 - Metrics for all events were displayed after every sample. Only display
   the metrics associated with the sampled event.

 - The new_line callback started a metric line that the next metric
   started again. Each metric starts its own line, so make it a no-op.

 - The metric value was written to stdout rather than the event's output
   file.

For example, with whitespace reduced:

  $ perf record -e '{cpu_core/instructions/,cpu_core/cycles/}:S' -a sleep 1
  $ perf script -F comm,cpu,event,period,metric

Before:

  perf [001]  2206 cpu_core/cycles/:
  perf [001] metric: 42972.00 tma_info_inst_mix_instructions
  perf [001] metric: perf [001] metric: 1.20 tma_info_thread_cpi
  perf [001] metric: perf [001] metric: 0.83 tma_info_thread_ipc
  perf [001] metric: 51762.00 tma_info_thread_clks
  perf [001]   804 cpu_core/instructions/:
  perf [001] metric: 18446744073709549568.00 tma_info_inst_mix_instructions

After:

  perf [001]  2206 cpu_core/cycles/:
  perf [001] metric: 67668.00 tma_info_thread_clks
  perf [001]   804 cpu_core/instructions/:
  perf [001] metric: 46758.00 tma_info_inst_mix_instructions
  perf [001] metric:     1.45 tma_info_thread_cpi
  perf [001] metric:     0.69 tma_info_thread_ipc

Before, the cpu_core/cycles/ sample is followed by the metrics of both
events, such as tma_info_inst_mix_instructions that only uses the
instructions event, rather than just tma_info_thread_clks. The CPI and
IPC lines repeat the "perf [001] metric:" prefix as script_new_line()
started a line that script_print_metric() started again. The following
cpu_core/instructions/ sample shows tma_info_inst_mix_instructions as
18446744073709549568, a negative delta, as the counts of the other CPUs
were left as deltas by the previous sample. The same deltas made the
before tma_info_thread_clks value of 51762 too small.

After, each sample is followed only by the metrics of the sampled event,
one per line, and the metric values are computed from the running totals
of the sample periods of all CPUs.

Fixes: 4bd1bef8bba2 ("perf script: Allow computing 'perf stat' style metrics")
Fixes: 3622990efaab ("perf script: Change metric format to use json metrics")
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/builtin-script.c | 32 ++++++++++++++++++--------------
 1 file changed, 18 insertions(+), 14 deletions(-)

diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index b6691ebb1b4e..c5a9651a197c 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2340,18 +2340,14 @@ static void script_print_metric(struct perf_stat_config *config __maybe_unused,
 	if (color)
 		color_fprintf(mctx->fp, color, fmt, val);
 	else
-		printf(fmt, val);
+		fprintf(mctx->fp, fmt, val);
 	fprintf(mctx->fp, " %s\n", unit);
 }
 
 static void script_new_line(struct perf_stat_config *config __maybe_unused,
-			    void *ctx)
+			    void *ctx __maybe_unused)
 {
-	struct metric_ctx *mctx = ctx;
-
-	perf_sample__fprintf_start(NULL, mctx->sample, mctx->thread, mctx->evsel,
-				   PERF_RECORD_SAMPLE, mctx->fp);
-	fputs("\tmetric: ", mctx->fp);
+	/* Each metric is printed on its own line by script_print_metric(). */
 }
 
 struct script_find_metrics_args {
@@ -2524,7 +2520,6 @@ static void perf_sample__fprint_metric(struct thread *thread,
 	};
 	struct perf_counts_values *count, *old_count;
 	int cpu_map_idx, thread_map_idx, aggr_idx;
-	struct evsel *pos;
 
 	if (!init_metrics) {
 		/* One time initialization of stat_config and metric data. */
@@ -2593,14 +2588,23 @@ static void perf_sample__fprint_metric(struct thread *thread,
 	/* Update the aggregated stats. */
 	perf_stat_process_counter(&stat_config, evsel);
 
-	/* Display all metrics. */
-	evlist__for_each_entry(evsel->evlist, pos) {
-		cpu_aggr_map__for_each_idx(aggr_idx, stat_config.aggr_map) {
-			perf_stat__print_shadow_stats(&stat_config, pos,
-						aggr_idx,
-						&ctx);
+	/*
+	 * Processing the counter replaced the counts with deltas from the
+	 * previous raw counts. Restore the running totals so that only the
+	 * next sample's period is added to the aggregated stats.
+	 */
+	for (unsigned int cpu_idx = 0; cpu_idx < perf_cpu_map__nr(evsel->core.cpus); cpu_idx++) {
+		for (int thread_idx = 0;
+		     thread_idx < perf_thread_map__nr(evsel->core.threads);
+		     thread_idx++) {
+			*perf_counts(evsel->counts, cpu_idx, thread_idx) =
+				*perf_counts(evsel->prev_raw_counts, cpu_idx, thread_idx);
 		}
 	}
+
+	/* Display the metrics associated with the sampled event. */
+	cpu_aggr_map__for_each_idx(aggr_idx, stat_config.aggr_map)
+		perf_stat__print_shadow_stats(&stat_config, evsel, aggr_idx, &ctx);
 }
 
 static bool show_event(struct perf_sample *sample,
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 03/30] perf script: Don't drop metric samples for CPUs missing from the evsel
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
  2026-11-13 19:53 ` [PATCH v2 01/30] perf pmus: Don't skip core PMUs as duplicates Ian Rogers
  2026-11-13 19:53 ` [PATCH v2 02/30] perf script: Fix metrics computed from samples Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:02   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 04/30] perf metricgroup: Don't prefer tool events for displaying a metric Ian Rogers
                   ` (26 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

perf script -F metric looks up the index of the sample's CPU in the
evsel's CPU map and, when it isn't present, prints "Missing CPU map
entry for CPU <n>" and ignores the sample. When the perf.data file
doesn't record the CPUs of an event, as is the case for perf record,
__perf_evlist__propagate_maps() sets the evsel's CPUs to the host's
online CPUs. If a CPU is offline when perf script is run, or the
perf.data file was recorded on a machine with more CPUs, then every
sample on such a CPU is dropped from the metrics with a message, for
example thousands of:

  Missing CPU map entry for CPU 1

perf script currently only supports global aggregation of the counts
used for metrics, the counts of all CPUs are summed. So place the counts
of a sample whose CPU isn't in the evsel's CPU map in the first CPU of
the map, as was already done for samples without a CPU. This doesn't
change the metric values computed for samples of the CPUs in the map,
the summed counts just include the previously dropped samples, and so
isn't a regression. Aggregating per CPU, and using the CPUs of the
perf.data file's environment rather than the host's, would be future
improvements.

Fixes: 3622990efaab ("perf script: Change metric format to use json metrics")
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/builtin-script.c | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index c5a9651a197c..97bad639753f 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2557,15 +2557,14 @@ static void perf_sample__fprint_metric(struct thread *thread,
 	/* Update the evsel's count using the sample's data. */
 	cpu_map_idx = perf_cpu_map__idx(evsel->core.cpus, (struct perf_cpu){sample->cpu});
 	if (cpu_map_idx < 0) {
-		/* Missing CPU, check for any CPU. */
-		if (perf_cpu_map__cpu(evsel->core.cpus, /*idx=*/0).cpu == -1 ||
-		    sample->cpu == (u32)-1) {
-			/* Place the counts in the which ever CPU is first in the map. */
-			cpu_map_idx = 0;
-		} else {
-			pr_info("Missing CPU map entry for CPU %d\n", sample->cpu);
-			return;
-		}
+		/*
+		 * The evsel's CPUs may not contain the sample's CPU, for
+		 * example the sample may have no CPU or, when the perf.data
+		 * file doesn't record the evsel's CPUs, the evsel's CPUs are
+		 * the host's online CPUs. As only global aggregation is
+		 * supported, place the counts in the first CPU of the map.
+		 */
+		cpu_map_idx = 0;
 	}
 	thread_map_idx = perf_thread_map__idx(evsel->core.threads, sample->tid);
 	if (thread_map_idx < 0) {
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 04/30] perf metricgroup: Don't prefer tool events for displaying a metric
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (2 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 03/30] perf script: Don't drop metric samples for CPUs missing from the evsel Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 19:59   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 05/30] perf env: Make lazy perf_env initialization thread-safe Ian Rogers
                   ` (25 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

pick_display_evsel() selects the event of a metric that perf stat shows
the metric alongside, preferring an event that isn't a tool event, such
as duration_time, and that is used by the fewest metrics. A tool event
used by fewer metrics replaced a non-tool event, so whether the metric
was displayed with a tool event depended on the order of the metric's
events. For example, CPUs_utilized could be shown alongside the
duration_time tool event rather than cpu-clock, and cpu-clock not shown.

Only compare the number of uses of events that are both, or both not,
tool events.

Fixes: c8035a496141 ("perf metricgroup: Add care to picking the evsel for displaying a metric")
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/metricgroup.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 779fe56fe8d0..98f3ba8f9533 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -1388,8 +1388,10 @@ static struct evsel *pick_display_evsel(struct list_head *metric_list,
 		struct evsel *candidate = metric_events[i];
 		size_t candidate_uses = count_uses(metric_list, candidate);
 
-		if ((selected_is_tool && !evsel__is_tool(candidate)) ||
-		    (candidate_uses < selected_uses)) {
+		bool candidate_is_tool = evsel__is_tool(candidate);
+
+		if ((selected_is_tool && !candidate_is_tool) ||
+		    (selected_is_tool == candidate_is_tool && candidate_uses < selected_uses)) {
 			selected = candidate;
 			selected_uses = candidate_uses;
 			selected_is_tool = evsel__is_tool(selected);
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 05/30] perf env: Make lazy perf_env initialization thread-safe
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (3 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 04/30] perf metricgroup: Don't prefer tool events for displaying a metric Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:05   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 06/30] perf env: Add host_env() singleton Ian Rogers
                   ` (24 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Guard lazy initialization of kernel_is_64_bit, nr_cpus_avail, e_machine,
cpuid, nr_pmu_mappings, pmu_mappings, and numa_map in struct perf_env
with env->lock, and use DO_ONCE() to initialize the cached host utsname
structure. This prevents data races when multiple threads concurrently
query a shared struct perf_env.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/env.c     | 178 +++++++++++++++++++++++++-------------
 tools/perf/util/session.c |  15 +++-
 2 files changed, 131 insertions(+), 62 deletions(-)

diff --git a/tools/perf/util/env.c b/tools/perf/util/env.c
index 50993b0dedc5..4ab46ca6b4ef 100644
--- a/tools/perf/util/env.c
+++ b/tools/perf/util/env.c
@@ -313,18 +313,17 @@ void perf_env__init(struct perf_env *env)
 	mutex_init(&env->lock);
 }
 
-static void perf_env__init_kernel_mode(struct perf_env *env)
+static const char *uts_machine(void)
 {
-	const char *arch = env->arch;
+	static struct utsname uts;
 
-	if (!arch) {
-		static struct utsname uts = { .machine[0] = '\0', };
+	DO_ONCE(uname, &uts);
+	return uts.machine[0] != '\0' ? uts.machine : NULL;
+}
 
-		if (uts.machine[0] == '\0')
-			uname(&uts);
-		if (uts.machine[0] != '\0')
-			arch = uts.machine;
-	}
+static void perf_env__init_kernel_mode(struct perf_env *env)
+{
+	const char *arch = env->arch ?: uts_machine();
 
 	if (arch) {
 		if (strstr(arch, "64") || strstr(arch, "s390x"))
@@ -340,10 +339,15 @@ static void perf_env__init_kernel_mode(struct perf_env *env)
 
 int perf_env__kernel_is_64_bit(struct perf_env *env)
 {
+	int is_64_bit;
+
+	mutex_lock(&env->lock);
 	if (env->kernel_is_64_bit == -1)
 		perf_env__init_kernel_mode(env);
+	is_64_bit = env->kernel_is_64_bit;
+	mutex_unlock(&env->lock);
 
-	return env->kernel_is_64_bit;
+	return is_64_bit;
 }
 
 bool perf_arch_is_big_endian(const char *arch)
@@ -474,7 +478,6 @@ int perf_env__read_pmu_mappings(struct perf_env *env)
 		pr_debug("pmu mappings not available\n");
 		return -ENOENT;
 	}
-	env->nr_pmu_mappings = pmu_num;
 
 	if (strbuf_init(&sb, 128 * pmu_num) < 0)
 		return -ENOMEM;
@@ -488,6 +491,7 @@ int perf_env__read_pmu_mappings(struct perf_env *env)
 	}
 
 	env->pmu_mappings = strbuf_detach(&sb, NULL);
+	env->nr_pmu_mappings = pmu_num;
 
 	return 0;
 
@@ -632,7 +636,17 @@ int perf_env__read_core_pmu_caps(struct perf_env *env)
 
 int perf_env__nr_cpus_avail(struct perf_env *env)
 {
-	return env && !perf_env__read_nr_cpus_avail(env) ? env->nr_cpus_avail : 0;
+	int nr_cpus = 0;
+
+	if (!env)
+		return 0;
+
+	mutex_lock(&env->lock);
+	if (!perf_env__read_nr_cpus_avail(env))
+		nr_cpus = env->nr_cpus_avail;
+	mutex_unlock(&env->lock);
+
+	return nr_cpus;
 }
 
 void cpu_cache_level__free(struct cpu_cache_level *cache)
@@ -780,7 +794,7 @@ static const char *e_machine_to_perf_arch(uint16_t e_machine)
 	return "unknown";
 }
 
-uint16_t perf_env__e_machine_nocache(struct perf_env *env, uint32_t *e_flags)
+static uint16_t __perf_env__e_machine_nocache(struct perf_env *env, uint32_t *e_flags)
 {
 	uint16_t e_machine = EM_NONE;
 	const char *arch = NULL;
@@ -794,14 +808,8 @@ uint16_t perf_env__e_machine_nocache(struct perf_env *env, uint32_t *e_flags)
 		is_64_bit = env->kernel_is_64_bit;
 	}
 
-	if (!arch) {
-		static struct utsname uts = { .machine[0] = '\0', };
-
-		if (uts.machine[0] == '\0')
-			uname(&uts);
-		if (uts.machine[0] != '\0')
-			arch = uts.machine;
-	}
+	if (!arch)
+		arch = uts_machine();
 
 	e_machine = perf_arch_to_e_machine(arch, is_64_bit);
 
@@ -811,18 +819,37 @@ uint16_t perf_env__e_machine_nocache(struct perf_env *env, uint32_t *e_flags)
 	return e_machine;
 }
 
+uint16_t perf_env__e_machine_nocache(struct perf_env *env, uint32_t *e_flags)
+{
+	uint16_t e_machine;
+
+	if (!env)
+		return __perf_env__e_machine_nocache(NULL, e_flags);
+
+	mutex_lock(&env->lock);
+	e_machine = __perf_env__e_machine_nocache(env, e_flags);
+	mutex_unlock(&env->lock);
+
+	return e_machine;
+}
+
 uint16_t perf_env__e_machine(struct perf_env *env, uint32_t *e_flags)
 {
 	uint16_t e_machine;
 	uint32_t local_e_flags = 0;
 
-	if (env && env->e_machine != EM_NONE) {
+	if (!env)
+		return __perf_env__e_machine_nocache(NULL, e_flags);
+
+	mutex_lock(&env->lock);
+	if (env->e_machine != EM_NONE) {
 		if (e_flags)
 			*e_flags = env->e_flags;
-
-		return env->e_machine;
+		e_machine = env->e_machine;
+		mutex_unlock(&env->lock);
+		return e_machine;
 	}
-	e_machine = perf_env__e_machine_nocache(env, &local_e_flags);
+	e_machine = __perf_env__e_machine_nocache(env, &local_e_flags);
 	/*
 	 * Only cache the e_machine in perf_env if env->arch is not NULL.
 	 * If env->arch is NULL, the e_machine is just a fallback to EM_HOST.
@@ -830,10 +857,12 @@ uint16_t perf_env__e_machine(struct perf_env *env, uint32_t *e_flags)
 	 * thread-based session e_machine scanning later in
 	 * perf_session__e_machine().
 	 */
-	if (env && env->arch) {
+	if (env->arch) {
 		env->e_machine = e_machine;
 		env->e_flags = local_e_flags;
 	}
+	mutex_unlock(&env->lock);
+
 	if (e_flags)
 		*e_flags = local_e_flags;
 
@@ -846,13 +875,11 @@ const char *perf_env__arch(struct perf_env *env)
 	const char *arch;
 
 	if (!env) {
-		static struct utsname uts = { .machine[0] = '\0', };
-		uint16_t host_e_machine;
+		const char *host_arch = uts_machine();
+
+		if (host_arch) {
+			uint16_t host_e_machine = perf_arch_to_e_machine(host_arch, -1);
 
-		if (uts.machine[0] == '\0')
-			uname(&uts);
-		if (uts.machine[0] != '\0') {
-			host_e_machine = perf_arch_to_e_machine(uts.machine, -1);
 			return e_machine_to_perf_arch(host_e_machine);
 		}
 		return e_machine_to_perf_arch(EM_HOST);
@@ -884,45 +911,60 @@ const char *perf_env__arch_strerrno(uint16_t e_machine, int err)
 
 const char *perf_env__cpuid(struct perf_env *env)
 {
-	int status;
+	const char *cpuid;
 
+	mutex_lock(&env->lock);
 	if (!env->cpuid) { /* Assume local operation */
-		status = perf_env__read_cpuid(env);
-		if (status)
+		if (perf_env__read_cpuid(env)) {
+			mutex_unlock(&env->lock);
 			return NULL;
+		}
 	}
+	cpuid = env->cpuid;
+	mutex_unlock(&env->lock);
 
-	return env->cpuid;
+	return cpuid;
 }
 
 int perf_env__nr_pmu_mappings(struct perf_env *env)
 {
-	int status;
+	int nr;
 
+	mutex_lock(&env->lock);
 	if (!env->nr_pmu_mappings) { /* Assume local operation */
-		status = perf_env__read_pmu_mappings(env);
-		if (status)
+		if (perf_env__read_pmu_mappings(env)) {
+			mutex_unlock(&env->lock);
 			return 0;
+		}
 	}
+	nr = env->nr_pmu_mappings;
+	mutex_unlock(&env->lock);
 
-	return env->nr_pmu_mappings;
+	return nr;
 }
 
 const char *perf_env__pmu_mappings(struct perf_env *env)
 {
-	int status;
+	const char *mappings;
 
+	mutex_lock(&env->lock);
 	if (!env->pmu_mappings) { /* Assume local operation */
-		status = perf_env__read_pmu_mappings(env);
-		if (status)
+		if (perf_env__read_pmu_mappings(env)) {
+			mutex_unlock(&env->lock);
 			return NULL;
+		}
 	}
+	mappings = env->pmu_mappings;
+	mutex_unlock(&env->lock);
 
-	return env->pmu_mappings;
+	return mappings;
 }
 
 int perf_env__numa_node(struct perf_env *env, struct perf_cpu cpu)
 {
+	int node;
+
+	mutex_lock(&env->lock);
 	if (!env->nr_numa_map) {
 		struct numa_node *nn;
 		int i, nr = 0;
@@ -939,8 +981,10 @@ int perf_env__numa_node(struct perf_env *env, struct perf_cpu cpu)
 		 * it for missing cpus, which return node -1
 		 */
 		env->numa_map = malloc(nr * sizeof(int));
-		if (!env->numa_map)
+		if (!env->numa_map) {
+			mutex_unlock(&env->lock);
 			return -1;
+		}
 
 		for (i = 0; i < nr; i++)
 			env->numa_map[i] = -1;
@@ -957,25 +1001,37 @@ int perf_env__numa_node(struct perf_env *env, struct perf_cpu cpu)
 		}
 	}
 
-	return cpu.cpu >= 0 && cpu.cpu < env->nr_numa_map ? env->numa_map[cpu.cpu] : -1;
+	node = cpu.cpu >= 0 && cpu.cpu < env->nr_numa_map ? env->numa_map[cpu.cpu] : -1;
+	mutex_unlock(&env->lock);
+
+	return node;
 }
 
 bool perf_env__has_pmu_mapping(struct perf_env *env, const char *pmu_name)
 {
-	char *pmu_mapping = env->pmu_mappings, *colon;
+	char *pmu_mapping, *colon;
+	bool ret = false;
+
+	mutex_lock(&env->lock);
+	pmu_mapping = env->pmu_mappings;
+	if (!pmu_mapping)
+		goto out_unlock;
 
 	for (int i = 0; i < env->nr_pmu_mappings; ++i) {
 		if (strtoul(pmu_mapping, &colon, 0) == ULONG_MAX || *colon != ':')
-			goto out_error;
+			goto out_unlock;
 
 		pmu_mapping = colon + 1;
-		if (strcmp(pmu_mapping, pmu_name) == 0)
-			return true;
+		if (strcmp(pmu_mapping, pmu_name) == 0) {
+			ret = true;
+			break;
+		}
 
 		pmu_mapping += strlen(pmu_mapping) + 1;
 	}
-out_error:
-	return false;
+out_unlock:
+	mutex_unlock(&env->lock);
+	return ret;
 }
 
 char *perf_env__find_pmu_cap(struct perf_env *env, const char *pmu_name,
@@ -1043,12 +1099,13 @@ void perf_env__find_br_cntr_info(struct perf_env *env,
 
 bool perf_env__is_x86_amd_cpu(struct perf_env *env)
 {
-	static int is_amd; /* 0: Uninitialized, 1: Yes, -1: No */
+	bool is_amd;
 
-	if (is_amd == 0)
-		is_amd = env->cpuid && strstarts(env->cpuid, "AuthenticAMD") ? 1 : -1;
+	mutex_lock(&env->lock);
+	is_amd = env->cpuid && strstarts(env->cpuid, "AuthenticAMD");
+	mutex_unlock(&env->lock);
 
-	return is_amd >= 1 ? true : false;
+	return is_amd;
 }
 
 bool x86__is_amd_cpu(void)
@@ -1066,12 +1123,13 @@ bool x86__is_amd_cpu(void)
 
 bool perf_env__is_x86_intel_cpu(struct perf_env *env)
 {
-	static int is_intel; /* 0: Uninitialized, 1: Yes, -1: No */
+	bool is_intel;
 
-	if (is_intel == 0)
-		is_intel = env->cpuid && strstarts(env->cpuid, "GenuineIntel") ? 1 : -1;
+	mutex_lock(&env->lock);
+	is_intel = env->cpuid && strstarts(env->cpuid, "GenuineIntel");
+	mutex_unlock(&env->lock);
 
-	return is_intel >= 1 ? true : false;
+	return is_intel;
 }
 
 bool x86__is_intel_cpu(void)
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 56a5e57ec512..f35d6867e816 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -4235,8 +4235,17 @@ uint16_t perf_session__e_machine(struct perf_session *session, uint32_t *e_flags
 	 * more accurate threads.
 	 */
 	env = perf_session__env(session);
-	if (env && env->e_machine != EM_NONE)
-		return perf_env__e_machine(env, e_flags);
+	if (env) {
+		uint16_t e_machine;
+
+		mutex_lock(&env->lock);
+		e_machine = env->e_machine;
+		if (e_machine != EM_NONE && e_flags)
+			*e_flags = env->e_flags;
+		mutex_unlock(&env->lock);
+		if (e_machine != EM_NONE)
+			return e_machine;
+	}
 
 	/*
 	 * Compute from threads, note this is more accurate than
@@ -4249,8 +4258,10 @@ uint16_t perf_session__e_machine(struct perf_session *session, uint32_t *e_flags
 
 	if (args.e_machine != EM_NONE) {
 		if (env) {
+			mutex_lock(&env->lock);
 			env->e_machine = args.e_machine;
 			env->e_flags = args.e_flags;
+			mutex_unlock(&env->lock);
 		}
 		if (e_flags)
 			*e_flags = args.e_flags;
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 06/30] perf env: Add host_env() singleton
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (4 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 05/30] perf env: Make lazy perf_env initialization thread-safe Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:47   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 07/30] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers
                   ` (23 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Add host_env() to provide a statically initialized singleton
struct perf_env representing the live host environment, replacing
ad-hoc stack and static host_env instances across subcommands,
utilities, benchmarks, and tests.

Drop the redundant struct perf_env * parameter from
machine__new_host(), machine__new_kallsyms(), machine__new_live(),
and __perf_session__new().

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/bench/synthesize.c         | 13 ++------
 tools/perf/builtin-buildid-list.c     |  5 +--
 tools/perf/builtin-inject.c           |  3 +-
 tools/perf/builtin-kallsyms.c         | 12 ++-----
 tools/perf/builtin-lock.c             |  6 +---
 tools/perf/builtin-top.c              | 17 +++-------
 tools/perf/builtin-trace.c            | 18 +++--------
 tools/perf/perf.c                     |  3 ++
 tools/perf/tests/code-reading.c       |  5 +--
 tools/perf/tests/dlfilter-test.c      |  5 +--
 tools/perf/tests/dwarf-unwind.c       |  5 +--
 tools/perf/tests/maps.c               | 11 ++-----
 tools/perf/tests/mmap-thread-lookup.c |  5 +--
 tools/perf/tests/symbols.c            |  7 +----
 tools/perf/util/debug.c               |  6 +---
 tools/perf/util/env.c                 | 45 +++++++++++++++++----------
 tools/perf/util/env.h                 | 10 ++++++
 tools/perf/util/header.c              |  3 ++
 tools/perf/util/machine.c             | 16 +++++-----
 tools/perf/util/machine.h             | 28 +++++++++++++++--
 tools/perf/util/parse-events.c        |  9 ++----
 tools/perf/util/probe-event.c         |  5 +--
 tools/perf/util/rwsem.h               |  6 ++++
 tools/perf/util/session.c             |  6 ++--
 tools/perf/util/session.h             | 22 +++++++++++--
 25 files changed, 131 insertions(+), 140 deletions(-)

diff --git a/tools/perf/bench/synthesize.c b/tools/perf/bench/synthesize.c
index 265d49a913d9..6aac1ccc7f4b 100644
--- a/tools/perf/bench/synthesize.c
+++ b/tools/perf/bench/synthesize.c
@@ -115,16 +115,12 @@ static int run_single_threaded(void)
 		.pid = "self",
 	};
 	struct perf_thread_map *threads;
-	struct perf_env host_env;
 	int err;
 
 	perf_set_singlethreaded();
-	perf_env__init(&host_env);
-	session = __perf_session__new(/*data=*/NULL, /*tool=*/NULL,
-				      /*trace_event_repipe=*/false, &host_env);
+	session = perf_session__new(/*data=*/NULL, /*tool=*/NULL);
 	if (IS_ERR(session)) {
 		pr_err("Session creation failed.\n");
-		perf_env__exit(&host_env);
 		return PTR_ERR(session);
 	}
 	threads = thread_map__new_by_pid(getpid());
@@ -149,7 +145,6 @@ static int run_single_threaded(void)
 		perf_thread_map__put(threads);
 
 	perf_session__delete(session);
-	perf_env__exit(&host_env);
 	return err;
 }
 
@@ -163,14 +158,11 @@ static int do_run_multi_threaded(struct target *target,
 	int err = 0;
 	struct stats time_stats, event_stats;
 	struct perf_session *session;
-	struct perf_env host_env;
 
-	perf_env__init(&host_env);
 	init_stats(&time_stats);
 	init_stats(&event_stats);
 	for (i = 0; i < multi_iterations; i++) {
-		session = __perf_session__new(/*data=*/NULL, /*tool=*/NULL,
-					      /*trace_event_repipe=*/false, &host_env);
+		session = perf_session__new(/*data=*/NULL, /*tool=*/NULL);
 		if (IS_ERR(session)) {
 			err = PTR_ERR(session);
 			goto err_out;
@@ -209,7 +201,6 @@ static int do_run_multi_threaded(struct target *target,
 	printf("    Average time per event %.3f usec\n",
 		time_average / event_average);
 err_out:
-	perf_env__exit(&host_env);
 	return err;
 }
 
diff --git a/tools/perf/builtin-buildid-list.c b/tools/perf/builtin-buildid-list.c
index 5555f5dcefe4..f6159300ee63 100644
--- a/tools/perf/builtin-buildid-list.c
+++ b/tools/perf/builtin-buildid-list.c
@@ -45,14 +45,11 @@ static int buildid__map_cb(struct map *map, void *arg __maybe_unused)
 
 static void buildid__show_kernel_maps(void)
 {
-	struct perf_env host_env;
 	struct machine *machine;
 
-	perf_env__init(&host_env);
-	machine = machine__new_host(&host_env);
+	machine = machine__new_host();
 	machine__for_each_kernel_map(machine, buildid__map_cb, NULL);
 	machine__delete(machine);
-	perf_env__exit(&host_env);
 }
 
 static int sysfs__fprintf_build_id(FILE *fp)
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
index ce7562fc788c..d115dc5ab6d6 100644
--- a/tools/perf/builtin-inject.c
+++ b/tools/perf/builtin-inject.c
@@ -2871,8 +2871,7 @@ int cmd_inject(int argc, const char **argv)
 		}
 	}
 	inject.session = __perf_session__new(&data, tool,
-					     /*trace_event_repipe=*/inject.output.is_pipe,
-					     /*host_env=*/NULL);
+					     /*trace_event_repipe=*/inject.output.is_pipe);
 
 	if (IS_ERR(inject.session)) {
 		ret = PTR_ERR(inject.session);
diff --git a/tools/perf/builtin-kallsyms.c b/tools/perf/builtin-kallsyms.c
index 3c4339982b16..e85beb380d9d 100644
--- a/tools/perf/builtin-kallsyms.c
+++ b/tools/perf/builtin-kallsyms.c
@@ -19,17 +19,10 @@
 
 static int __cmd_kallsyms(int argc, const char **argv)
 {
-	int i, err;
-	struct perf_env host_env;
+	int i, err = 0;
 	struct machine *machine = NULL;
 
-
-	perf_env__init(&host_env);
-	err = perf_env__set_cmdline(&host_env, argc, argv);
-	if (err)
-		goto out;
-
-	machine = machine__new_kallsyms(&host_env);
+	machine = machine__new_kallsyms();
 	if (machine == NULL) {
 		pr_err("Couldn't read /proc/kallsyms\n");
 		err = -1;
@@ -54,7 +47,6 @@ static int __cmd_kallsyms(int argc, const char **argv)
 	}
 out:
 	machine__delete(machine);
-	perf_env__exit(&host_env);
 	return err;
 }
 
diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c
index 778923c92f42..6d557c552f36 100644
--- a/tools/perf/builtin-lock.c
+++ b/tools/perf/builtin-lock.c
@@ -2015,7 +2015,6 @@ static int __cmd_contention(int argc, const char **argv)
 		.owner = show_lock_owner,
 		.cgroups = RB_ROOT,
 	};
-	struct perf_env host_env;
 
 	lockhash_table = calloc(LOCKHASH_SIZE, sizeof(*lockhash_table));
 	if (!lockhash_table)
@@ -2032,9 +2031,7 @@ static int __cmd_contention(int argc, const char **argv)
 	eops.mmap2		 = perf_event__process_mmap2;
 	eops.tracing_data	 = perf_event__process_tracing_data;
 
-	perf_env__init(&host_env);
-	session = __perf_session__new(use_bpf ? NULL : &data, &eops,
-				/*trace_event_repipe=*/false, &host_env);
+	session = perf_session__new(use_bpf ? NULL : &data, &eops);
 
 	if (IS_ERR(session)) {
 		pr_err("Initializing perf session failed\n");
@@ -2157,7 +2154,6 @@ static int __cmd_contention(int argc, const char **argv)
 	evlist__put(con.evlist);
 	lock_contention_finish(&con);
 	perf_session__delete(session);
-	perf_env__exit(&host_env);
 	zfree(&lockhash_table);
 	return err;
 }
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index a3442d2d6edf..e52ee42d7e0f 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -1658,7 +1658,6 @@ int cmd_top(int argc, const char **argv)
 		NULL
 	};
 	int status = hists__init();
-	struct perf_env host_env;
 
 	if (status < 0)
 		return status;
@@ -1672,7 +1671,6 @@ int cmd_top(int argc, const char **argv)
 	if (top.evlist == NULL)
 		return -ENOMEM;
 
-	perf_env__init(&host_env);
 	status = perf_config(perf_top_config, &top);
 	if (status)
 		goto out_put_evlist;
@@ -1680,11 +1678,7 @@ int cmd_top(int argc, const char **argv)
 	 * Since the per arch annotation init routine may need the cpuid, read
 	 * it here, since we are not getting this from the perf.data header.
 	 */
-	status = perf_env__set_cmdline(&host_env, argc, argv);
-	if (status)
-		goto out_put_evlist;
-
-	status = perf_env__read_cpuid(&host_env);
+	status = perf_env__read_cpuid(host_env());
 	if (status) {
 		/*
 		 * Some arches do not provide a get_cpuid(), so just use pr_debug, otherwise
@@ -1803,7 +1797,7 @@ int cmd_top(int argc, const char **argv)
 		symbol_conf.show_branchflag_count = true;
 
 	if (opts->branch_stack) {
-		status = perf_env__read_core_pmu_caps(&host_env);
+		status = perf_env__read_core_pmu_caps(host_env());
 		if (status) {
 			pr_err("PMU capability data is not available\n");
 			goto out_put_evlist;
@@ -1823,9 +1817,7 @@ int cmd_top(int argc, const char **argv)
 
 	setup_browser(false);
 
-	top.session = __perf_session__new(/*data=*/NULL, /*tool=*/NULL,
-					  /*trace_event_repipe=*/false,
-					  &host_env);
+	top.session = perf_session__new(/*data=*/NULL, /*tool=*/NULL);
 	if (IS_ERR(top.session)) {
 		status = PTR_ERR(top.session);
 		top.session = NULL;
@@ -1912,7 +1904,7 @@ int cmd_top(int argc, const char **argv)
 			goto out_put_evlist;
 		}
 
-		if (evlist__add_bpf_sb_event(top.sb_evlist, &host_env)) {
+		if (evlist__add_bpf_sb_event(top.sb_evlist, host_env())) {
 			pr_err("Couldn't ask for PERF_RECORD_BPF_EVENT side band events.\n.");
 			status = -EINVAL;
 			evlist__put(top.sb_evlist);
@@ -1936,7 +1928,6 @@ int cmd_top(int argc, const char **argv)
 	evlist__put(top.evlist);
 	perf_session__delete(top.session);
 	annotation_options__exit();
-	perf_env__exit(&host_env);
 
 	return status;
 }
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index 4094141461f9..fb02fa6dd437 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -142,7 +142,6 @@ struct syscall_fmt {
 };
 
 struct trace {
-	struct perf_env		host_env;
 	struct perf_tool	tool;
 	struct {
 		/** Sorted sycall numbers used by the trace. */
@@ -2121,20 +2120,14 @@ static char *trace__machine__resolve_kernel_addr(void *vmachine, unsigned long l
 	return machine__resolve_kernel_addr(vmachine, addrp, modp);
 }
 
-static int trace__symbols_init(struct trace *trace, int argc, const char **argv,
-			       struct evlist *evlist)
+static int trace__symbols_init(struct trace *trace, struct evlist *evlist)
 {
 	int err = symbol__init(NULL);
 
 	if (err)
 		return err;
 
-	perf_env__init(&trace->host_env);
-	err = perf_env__set_cmdline(&trace->host_env, argc, argv);
-	if (err)
-		goto out;
-
-	trace->host = machine__new_host(&trace->host_env);
+	trace->host = machine__new_host();
 	if (trace->host == NULL) {
 		err = -ENOMEM;
 		goto out;
@@ -2155,10 +2148,8 @@ static int trace__symbols_init(struct trace *trace, int argc, const char **argv,
 					    /*mmap_data=*/false,
 					    /*nr_threads_synthesize=*/1);
 out:
-	if (err) {
-		perf_env__exit(&trace->host_env);
+	if (err)
 		symbol__exit();
-	}
 	return err;
 }
 
@@ -2167,7 +2158,6 @@ static void trace__symbols__exit(struct trace *trace)
 	machine__delete(trace->host);
 	trace->host = NULL;
 
-	perf_env__exit(&trace->host_env);
 	symbol__exit();
 }
 
@@ -4833,7 +4823,7 @@ static int trace__run(struct trace *trace, int argc, const char **argv)
 		goto out_put_evlist;
 	}
 
-	err = trace__symbols_init(trace, argc, argv, evlist);
+	err = trace__symbols_init(trace, evlist);
 	if (err < 0) {
 		fprintf(trace->output, "Problems initializing symbol libraries!\n");
 		goto out_put_evlist;
diff --git a/tools/perf/perf.c b/tools/perf/perf.c
index d60bc929c257..bcbc12295a38 100644
--- a/tools/perf/perf.c
+++ b/tools/perf/perf.c
@@ -32,6 +32,7 @@
 #include "util/build-id.h"
 #include "util/config.h"
 #include "util/debug.h"
+#include "util/env.h"
 #ifdef HAVE_LIBTRACEEVENT
 #include "util/trace-event.h"
 #endif
@@ -380,12 +381,14 @@ static int run_builtin(const struct cmd_struct *p, int argc, const char **argv)
 		use_pager = 1;
 	commit_pager_choice();
 
+	perf_env__set_cmdline(host_env(), argc, argv);
 	status = p->fn(argc, argv);
 	perf_config__exit();
 #ifdef HAVE_LIBTRACEEVENT
 	trace_event__exit();
 #endif
 	exit_browser(status);
+	perf_env__exit(host_env());
 
 	if (status)
 		return status & 0xff;
diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c
index 130cd6021f93..ac21b083eadc 100644
--- a/tools/perf/tests/code-reading.c
+++ b/tools/perf/tests/code-reading.c
@@ -710,12 +710,10 @@ static int do_test_code_reading(bool try_kcore)
 	struct dso *dso;
 	const char *events[] = { "cpu-cycles", "cpu-cycles:u", "cpu-clock", "cpu-clock:u", NULL };
 	int evidx = 0;
-	struct perf_env host_env;
 
 	pid = getpid();
 
-	perf_env__init(&host_env);
-	machine = machine__new_host(&host_env);
+	machine = machine__new_host();
 	if (!machine) {
 		pr_debug("machine__new_host failed\n");
 		goto out_err;
@@ -851,7 +849,6 @@ static int do_test_code_reading(bool try_kcore)
 	perf_cpu_map__put(cpus);
 	perf_thread_map__put(threads);
 	machine__delete(machine);
-	perf_env__exit(&host_env);
 	tested_sections__free(&tested_sections);
 
 	return err;
diff --git a/tools/perf/tests/dlfilter-test.c b/tools/perf/tests/dlfilter-test.c
index 204663571943..312fc7e8c49a 100644
--- a/tools/perf/tests/dlfilter-test.c
+++ b/tools/perf/tests/dlfilter-test.c
@@ -322,7 +322,6 @@ static int run_perf_script(struct test_data *td)
 
 static int test__dlfilter_test(struct test_data *td)
 {
-	struct perf_env host_env;
 	u64 sample_type = TEST_SAMPLE_TYPE;
 	pid_t pid = 12345;
 	pid_t tid = 12346;
@@ -356,8 +355,7 @@ static int test__dlfilter_test(struct test_data *td)
 		return test_result("Failed to find program symbols", TEST_FAIL);
 
 	pr_debug("Creating new host machine structure\n");
-	perf_env__init(&host_env);
-	td->machine = machine__new_host(&host_env);
+	td->machine = machine__new_host();
 
 	td->fd = creat(td->perf_data_file_name, 0644);
 	if (td->fd < 0)
@@ -390,7 +388,6 @@ static int test__dlfilter_test(struct test_data *td)
 
 	err = run_perf_script(td) ? TEST_FAIL : TEST_OK;
 out:
-	perf_env__exit(&host_env);
 	return err;
 }
 
diff --git a/tools/perf/tests/dwarf-unwind.c b/tools/perf/tests/dwarf-unwind.c
index 9ed78d00fb87..d324c9c09b0b 100644
--- a/tools/perf/tests/dwarf-unwind.c
+++ b/tools/perf/tests/dwarf-unwind.c
@@ -181,7 +181,6 @@ NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__krava_1(struct thread *th
 noinline int test__dwarf_unwind(struct test_suite *test __maybe_unused,
 				int subtest __maybe_unused)
 {
-	struct perf_env host_env;
 	struct machine *machine;
 	struct thread *thread;
 	int err = -1;
@@ -190,8 +189,7 @@ noinline int test__dwarf_unwind(struct test_suite *test __maybe_unused,
 	callchain_param.record_mode = CALLCHAIN_DWARF;
 	dwarf_callchain_users = true;
 
-	perf_env__init(&host_env);
-	machine = machine__new_live(&host_env, /*kernel_maps=*/true, pid);
+	machine = machine__new_live(/*kernel_maps=*/true, pid);
 	if (!machine) {
 		pr_err("Could not get machine\n");
 		goto out;
@@ -216,7 +214,6 @@ noinline int test__dwarf_unwind(struct test_suite *test __maybe_unused,
 
  out:
 	machine__delete(machine);
-	perf_env__exit(&host_env);
 	return err;
 }
 
diff --git a/tools/perf/tests/maps.c b/tools/perf/tests/maps.c
index ff6d25f9c043..2fbe61e1a64c 100644
--- a/tools/perf/tests/maps.c
+++ b/tools/perf/tests/maps.c
@@ -5,7 +5,6 @@
 #include <linux/kernel.h>
 #include "debug.h"
 #include "dso.h"
-#include "env.h"
 #include "map.h"
 #include "maps.h"
 #include "tests.h"
@@ -102,12 +101,10 @@ static int test__maps__merge_in(struct test_suite *t __maybe_unused, int subtest
 	};
 	struct map *map_kcore1, *map_kcore2, *map_kcore3;
 	int ret;
-	struct perf_env host_env;
 	struct machine *machine;
 	struct maps *maps;
 
-	perf_env__init(&host_env);
-	machine = machine__new_live(&host_env, /*kernel_maps=*/false, getpid());
+	machine = machine__new_live(/*kernel_maps=*/false, getpid());
 	maps = maps__new(machine);
 
 	TEST_ASSERT_VAL("failed to create machine", machine);
@@ -169,7 +166,6 @@ static int test__maps__merge_in(struct test_suite *t __maybe_unused, int subtest
 	map__zput(map_kcore2);
 	map__zput(map_kcore3);
 	machine__delete(machine);
-	perf_env__exit(&host_env);
 	return TEST_OK;
 }
 
@@ -200,12 +196,10 @@ static int test__maps__fixup_overlap_and_insert(struct test_suite *t __maybe_unu
 	struct map *map_split, *map_eclipse;
 	int ret;
 	unsigned int i;
-	struct perf_env host_env;
 	struct machine *machine;
 	struct maps *maps;
 
-	perf_env__init(&host_env);
-	machine = machine__new_live(&host_env, /*kernel_maps=*/false, getpid());
+	machine = machine__new_live(/*kernel_maps=*/false, getpid());
 	maps = maps__new(machine);
 
 	TEST_ASSERT_VAL("failed to create machine", machine);
@@ -249,7 +243,6 @@ static int test__maps__fixup_overlap_and_insert(struct test_suite *t __maybe_unu
 
 	maps__zput(maps);
 	machine__delete(machine);
-	perf_env__exit(&host_env);
 	return TEST_OK;
 }
 
diff --git a/tools/perf/tests/mmap-thread-lookup.c b/tools/perf/tests/mmap-thread-lookup.c
index 0c5619c6e6e9..7559123983c5 100644
--- a/tools/perf/tests/mmap-thread-lookup.c
+++ b/tools/perf/tests/mmap-thread-lookup.c
@@ -156,7 +156,6 @@ static int synth_process(struct machine *machine)
 
 static int mmap_events(synth_cb synth)
 {
-	struct perf_env host_env;
 	struct machine *machine;
 	int err, i;
 
@@ -169,8 +168,7 @@ static int mmap_events(synth_cb synth)
 	 */
 	TEST_ASSERT_VAL("failed to create threads", !threads_create());
 
-	perf_env__init(&host_env);
-	machine = machine__new_host(&host_env);
+	machine = machine__new_host();
 
 	dump_trace = verbose > 1 ? 1 : 0;
 
@@ -212,7 +210,6 @@ static int mmap_events(synth_cb synth)
 	}
 
 	machine__delete(machine);
-	perf_env__exit(&host_env);
 	return err;
 }
 
diff --git a/tools/perf/tests/symbols.c b/tools/perf/tests/symbols.c
index d3180db45ee6..dce397f92006 100644
--- a/tools/perf/tests/symbols.c
+++ b/tools/perf/tests/symbols.c
@@ -18,18 +18,15 @@
 #include "tests.h"
 
 struct test_info {
-	struct perf_env host_env;
 	struct machine *machine;
 	struct thread *thread;
 };
 
 static int init_test_info(struct test_info *ti)
 {
-	perf_env__init(&ti->host_env);
-	ti->machine = machine__new_host(&ti->host_env);
+	ti->machine = machine__new_host();
 	if (!ti->machine) {
 		pr_debug("machine__new_host() failed!\n");
-		perf_env__exit(&ti->host_env);
 		return TEST_FAIL;
 	}
 
@@ -37,7 +34,6 @@ static int init_test_info(struct test_info *ti)
 	ti->thread = machine__findnew_thread(ti->machine, 100, 100);
 	if (!ti->thread) {
 		pr_debug("machine__findnew_thread() failed!\n");
-		perf_env__exit(&ti->host_env);
 		return TEST_FAIL;
 	}
 
@@ -48,7 +44,6 @@ static void exit_test_info(struct test_info *ti)
 {
 	thread__put(ti->thread);
 	machine__delete(ti->machine);
-	perf_env__exit(&ti->host_env);
 }
 
 struct dso_map {
diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c
index b7095519419e..dfba9ddeacb7 100644
--- a/tools/perf/util/debug.c
+++ b/tools/perf/util/debug.c
@@ -312,10 +312,8 @@ void __dump_stack(FILE *file, void **stackdump, size_t stackdump_size)
 	pid_t pid = getpid();
 	struct machine *machine;
 	struct thread *thread = NULL;
-	struct perf_env host_env;
 
-	perf_env__init(&host_env);
-	machine = machine__new_live(&host_env, /*kernel_maps=*/false, pid);
+	machine = machine__new_live(/*kernel_maps=*/false, pid);
 
 	if (machine)
 		thread = machine__find_thread(machine, pid, pid);
@@ -328,7 +326,6 @@ void __dump_stack(FILE *file, void **stackdump, size_t stackdump_size)
 		 */
 		backtrace_symbols_fd(stackdump, stackdump_size, fileno(file));
 		machine__delete(machine);
-		perf_env__exit(&host_env);
 		return;
 	}
 #endif
@@ -355,7 +352,6 @@ void __dump_stack(FILE *file, void **stackdump, size_t stackdump_size)
 	}
 	thread__put(thread);
 	machine__delete(machine);
-	perf_env__exit(&host_env);
 }
 
 /* Obtain a backtrace and print it to stdout. */
diff --git a/tools/perf/util/env.c b/tools/perf/util/env.c
index 4ab46ca6b4ef..1fe6ce84e607 100644
--- a/tools/perf/util/env.c
+++ b/tools/perf/util/env.c
@@ -13,6 +13,7 @@
 #include <linux/zalloc.h>
 #include "cgroup.h"
 #include <errno.h>
+#include <pthread.h>
 #include <sys/utsname.h>
 #include <stdlib.h>
 #include <string.h>
@@ -246,6 +247,22 @@ void free_cpu_domain_info(struct cpu_domain_map **cd_map, u32 schedstat_version,
 	zfree(&cd_map);
 }
 
+static struct perf_env __host_env = {
+#ifdef HAVE_LIBBPF_SUPPORT
+	.bpf_progs = {
+		.lock = __PERF_RWSEM_INITIALIZER,
+		.infos = RB_ROOT,
+		.btfs = RB_ROOT,
+	},
+#endif
+	.cgroups = {
+		.lock = __PERF_RWSEM_INITIALIZER,
+		.tree = RB_ROOT,
+	},
+	.kernel_is_64_bit = -1,
+	.lock = { .lock = __PERF_MUTEX_INITIALIZER },
+};
+
 void perf_env__exit(struct perf_env *env)
 {
 	int i, j;
@@ -313,6 +330,11 @@ void perf_env__init(struct perf_env *env)
 	mutex_init(&env->lock);
 }
 
+struct perf_env *host_env(void)
+{
+	return &__host_env;
+}
+
 static const char *uts_machine(void)
 {
 	static struct utsname uts;
@@ -409,6 +431,7 @@ int perf_env__set_cmdline(struct perf_env *env, int argc, const char *argv[])
 	int i;
 
 	/* do not include NULL termination */
+	zfree(&env->cmdline_argv);
 	env->cmdline_argv = calloc(argc, sizeof(char *));
 	if (env->cmdline_argv == NULL)
 		goto out_enomem;
@@ -1110,15 +1133,10 @@ bool perf_env__is_x86_amd_cpu(struct perf_env *env)
 
 bool x86__is_amd_cpu(void)
 {
-	struct perf_env env = { .total_mem = 0, };
-	bool is_amd;
+	struct perf_env *env = host_env();
 
-	perf_env__init(&env);
-	perf_env__cpuid(&env);
-	is_amd = perf_env__is_x86_amd_cpu(&env);
-	perf_env__exit(&env);
-
-	return is_amd;
+	perf_env__cpuid(env);
+	return perf_env__is_x86_amd_cpu(env);
 }
 
 bool perf_env__is_x86_intel_cpu(struct perf_env *env)
@@ -1134,13 +1152,8 @@ bool perf_env__is_x86_intel_cpu(struct perf_env *env)
 
 bool x86__is_intel_cpu(void)
 {
-	struct perf_env env = { .total_mem = 0, };
-	bool is_intel;
-
-	perf_env__init(&env);
-	perf_env__cpuid(&env);
-	is_intel = perf_env__is_x86_intel_cpu(&env);
-	perf_env__exit(&env);
+	struct perf_env *env = host_env();
 
-	return is_intel;
+	perf_env__cpuid(env);
+	return perf_env__is_x86_intel_cpu(env);
 }
diff --git a/tools/perf/util/env.h b/tools/perf/util/env.h
index 23d35425efe3..134102148a5c 100644
--- a/tools/perf/util/env.h
+++ b/tools/perf/util/env.h
@@ -221,6 +221,16 @@ const char *perf_env__cpuid(struct perf_env *env);
 int perf_env__nr_cpus_avail(struct perf_env *env);
 
 void perf_env__init(struct perf_env *env);
+
+/**
+ * host_env() - Return the singleton struct perf_env for the live host.
+ *
+ * Returns a pointer to the statically initialized global struct perf_env
+ * representing the live host.
+ *
+ * Return: Pointer to the live host's struct perf_env.
+ */
+struct perf_env *host_env(void);
 #ifdef HAVE_LIBBPF_SUPPORT
 bool __perf_env__insert_bpf_prog_info(struct perf_env *env,
 				      struct bpf_prog_info_node *info_node);
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
index 9eec6deee190..b6f86f557caf 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -670,6 +670,9 @@ static int write_cmdline(struct feat_fd *ff,
 	char pbuf[MAXPATHLEN], *buf;
 	int i, ret, n;
 
+	if (!env->cmdline_argv)
+		env = host_env();
+
 	/* actual path to perf binary */
 	buf = perf_exe(pbuf, MAXPATHLEN);
 
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index 5eb5efa4eb90..999dd3e3c643 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -130,7 +130,7 @@ int machine__init(struct machine *machine, const char *root_dir, pid_t pid)
 	return err;
 }
 
-static struct machine *__machine__new_host(struct perf_env *host_env, bool kernel_maps)
+static struct machine *__machine__new_host(bool kernel_maps)
 {
 	struct machine *machine = malloc(sizeof(*machine));
 
@@ -146,13 +146,13 @@ static struct machine *__machine__new_host(struct perf_env *host_env, bool kerne
 		free(machine);
 		return NULL;
 	}
-	machine->env = host_env;
+	machine->env = host_env();
 	return machine;
 }
 
-struct machine *machine__new_host(struct perf_env *host_env)
+struct machine *machine__new_host(void)
 {
-	return __machine__new_host(host_env, /*kernel_maps=*/true);
+	return __machine__new_host(/*kernel_maps=*/true);
 }
 
 static int mmap_handler(const struct perf_tool *tool __maybe_unused,
@@ -172,9 +172,9 @@ static int machine__init_live(struct machine *machine, pid_t pid)
 						  mmap_handler, machine, true);
 }
 
-struct machine *machine__new_live(struct perf_env *host_env, bool kernel_maps, pid_t pid)
+struct machine *machine__new_live(bool kernel_maps, pid_t pid)
 {
-	struct machine *machine = __machine__new_host(host_env, kernel_maps);
+	struct machine *machine = __machine__new_host(kernel_maps);
 
 	if (!machine)
 		return NULL;
@@ -186,9 +186,9 @@ struct machine *machine__new_live(struct perf_env *host_env, bool kernel_maps, p
 	return machine;
 }
 
-struct machine *machine__new_kallsyms(struct perf_env *host_env)
+struct machine *machine__new_kallsyms(void)
 {
-	struct machine *machine = machine__new_host(host_env);
+	struct machine *machine = machine__new_host();
 	/*
 	 * FIXME:
 	 * 1) We should switch to machine__load_kallsyms(), i.e. not explicitly
diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h
index 973a59f7c26a..be562b989c26 100644
--- a/tools/perf/util/machine.h
+++ b/tools/perf/util/machine.h
@@ -177,9 +177,31 @@ struct thread *machine__findnew_guest_code(struct machine *machine, pid_t pid);
 void machines__set_id_hdr_size(struct machines *machines, u16 id_hdr_size);
 void machines__set_comm_exec(struct machines *machines, bool comm_exec);
 
-struct machine *machine__new_host(struct perf_env *host_env);
-struct machine *machine__new_kallsyms(struct perf_env *host_env);
-struct machine *machine__new_live(struct perf_env *host_env, bool kernel_maps, pid_t pid);
+/**
+ * machine__new_host() - Allocate and initialize a struct machine for the live
+ *                       host with kernel maps populated.
+ *
+ * Return: Pointer to the newly allocated struct machine, or NULL on failure.
+ */
+struct machine *machine__new_host(void);
+
+/**
+ * machine__new_kallsyms() - Allocate and initialize a struct machine for the
+ *                           live host and load /proc/kallsyms.
+ *
+ * Return: Pointer to the newly allocated struct machine, or NULL on failure.
+ */
+struct machine *machine__new_kallsyms(void);
+
+/**
+ * machine__new_live() - Allocate and initialize a struct machine for live
+ *                       inspection of @pid on the host.
+ * @kernel_maps: Whether to create kernel maps on the machine.
+ * @pid: Process ID whose mmap events should be synthesized.
+ *
+ * Return: Pointer to the newly allocated struct machine, or NULL on failure.
+ */
+struct machine *machine__new_live(bool kernel_maps, pid_t pid);
 int machine__init(struct machine *machine, const char *root_dir, pid_t pid);
 void machine__exit(struct machine *machine);
 void machine__delete_threads(struct machine *machine);
diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c
index f3ba70d4f76b..5ca6b93d8a2a 100644
--- a/tools/perf/util/parse-events.c
+++ b/tools/perf/util/parse-events.c
@@ -583,13 +583,8 @@ size_t default_breakpoint_len(void)
 #if defined(__i386__)
 	static int len;
 
-	if (len == 0) {
-		struct perf_env env = {};
-
-		perf_env__init(&env);
-		len = perf_env__kernel_is_64_bit(&env) ? sizeof(u64) : sizeof(long);
-		perf_env__exit(&env);
-	}
+	if (len == 0)
+		len = perf_env__kernel_is_64_bit(host_env()) ? sizeof(u64) : sizeof(long);
 	return len;
 #elif defined(__aarch64__)
 	return 4;
diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c
index 1534280c0c1d..d042d8f69f59 100644
--- a/tools/perf/util/probe-event.c
+++ b/tools/perf/util/probe-event.c
@@ -74,14 +74,12 @@ int e_snprintf(char *str, size_t size, const char *format, ...)
 }
 
 static struct machine *host_machine;
-static struct perf_env host_env;
 
 /* Initialize symbol maps and path of vmlinux/modules */
 int init_probe_symbol_maps(bool user_only)
 {
 	int ret;
 
-	perf_env__init(&host_env);
 	symbol_conf.allow_aliases = true;
 	ret = symbol__init(NULL);
 	if (ret < 0) {
@@ -95,7 +93,7 @@ int init_probe_symbol_maps(bool user_only)
 	if (symbol_conf.vmlinux_name)
 		pr_debug("Use vmlinux: %s\n", symbol_conf.vmlinux_name);
 
-	host_machine = machine__new_host(&host_env);
+	host_machine = machine__new_host();
 	if (!host_machine) {
 		pr_debug("machine__new_host() failed.\n");
 		symbol__exit();
@@ -112,7 +110,6 @@ void exit_probe_symbol_maps(void)
 	machine__delete(host_machine);
 	host_machine = NULL;
 	symbol__exit();
-	perf_env__exit(&host_env);
 }
 
 static struct ref_reloc_sym *kernel_get_ref_reloc_sym(struct map **pmap)
diff --git a/tools/perf/util/rwsem.h b/tools/perf/util/rwsem.h
index b102d8143181..d2bf722e409c 100644
--- a/tools/perf/util/rwsem.h
+++ b/tools/perf/util/rwsem.h
@@ -18,6 +18,12 @@ struct LOCKABLE rw_semaphore {
 #endif
 };
 
+#if RWS_ERRORCHECK
+#define __PERF_RWSEM_INITIALIZER { .mtx = { .lock = __PERF_MUTEX_INITIALIZER } }
+#else
+#define __PERF_RWSEM_INITIALIZER { .lock = PTHREAD_RWLOCK_INITIALIZER }
+#endif
+
 int init_rwsem(struct rw_semaphore *sem);
 int exit_rwsem(struct rw_semaphore *sem);
 
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index f35d6867e816..6ff8d97dcf7d 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -151,8 +151,7 @@ static int ordered_events__deliver_event(struct ordered_events *oe,
 
 struct perf_session *__perf_session__new(struct perf_data *data,
 					 struct perf_tool *tool,
-					 bool trace_event_repipe,
-					 struct perf_env *host_env)
+					 bool trace_event_repipe)
 {
 	int ret = -ENOMEM;
 	struct perf_session *session = zalloc(sizeof(*session));
@@ -206,8 +205,7 @@ struct perf_session *__perf_session__new(struct perf_data *data,
 				symbol_conf.kallsyms_name = perf_data__kallsyms_name(data);
 		}
 	} else  {
-		assert(host_env != NULL);
-		session->machines.host.env = host_env;
+		session->machines.host.env = host_env();
 	}
 	if (session->evlist)
 		evlist__set_session(session->evlist, session);
diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h
index ac5803d5fb4e..1e0bef9951d1 100644
--- a/tools/perf/util/session.h
+++ b/tools/perf/util/session.h
@@ -107,15 +107,31 @@ struct decomp {
 
 struct perf_tool;
 
+/**
+ * __perf_session__new() - Allocate and initialize a new struct perf_session.
+ * @data: Input/output perf_data descriptor, or NULL for a live host session.
+ * @tool: Event processing callbacks, or NULL.
+ * @trace_event_repipe: Whether to repipe trace event records to stdout.
+ *
+ * When @data is NULL, associates the session's host machine with host_env().
+ *
+ * Return: Pointer to the new session, or an ERR_PTR() on failure.
+ */
 struct perf_session *__perf_session__new(struct perf_data *data,
 					 struct perf_tool *tool,
-					 bool trace_event_repipe,
-					 struct perf_env *host_env);
+					 bool trace_event_repipe);
 
+/**
+ * perf_session__new() - Allocate and initialize a new struct perf_session.
+ * @data: Input/output perf_data descriptor, or NULL for a live host session.
+ * @tool: Event processing callbacks, or NULL.
+ *
+ * Return: Pointer to the new session, or an ERR_PTR() on failure.
+ */
 static inline struct perf_session *perf_session__new(struct perf_data *data,
 						     struct perf_tool *tool)
 {
-	return __perf_session__new(data, tool, /*trace_event_repipe=*/false, /*host_env=*/NULL);
+	return __perf_session__new(data, tool, /*trace_event_repipe=*/false);
 }
 
 void perf_session__delete(struct perf_session *session);
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 07/30] perf pmu: Add from_env parameter to perf_pmu__init()
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (5 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 06/30] perf env: Add host_env() singleton Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:10   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 08/30] perf pmus: Introduce struct perf_pmus and perf_env__pmus() Ian Rogers
                   ` (22 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Add a boolean from_env field to struct perf_pmu initialized via
perf_pmu__init() to distinguish PMUs reconstructed from perf_env
headers from live host PMUs. Guard tp_pmu, hwmon_pmu, and drm_pmu
callbacks on !pmu->from_env so perf.data-derived PMUs do not access
host tracepoints or cast plain struct perf_pmu allocations via
container_of(). The hwmon and drm evsel open and read callbacks fail
with -EINVAL for such PMUs.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/tests/pmu-events.c |  4 ++--
 tools/perf/util/drm_pmu.c     | 16 ++++++++++----
 tools/perf/util/hwmon_pmu.c   | 16 +++++++++++---
 tools/perf/util/pmu.c         | 41 +++++++++++++++++++----------------
 tools/perf/util/pmu.h         | 13 ++++++++++-
 tools/perf/util/tool_pmu.c    |  2 +-
 6 files changed, 62 insertions(+), 30 deletions(-)

diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c
index e6e4f4cf1fa2..acf8e8ee1237 100644
--- a/tools/perf/tests/pmu-events.c
+++ b/tools/perf/tests/pmu-events.c
@@ -514,7 +514,7 @@ static int __test_core_pmu_event_aliases(const char *pmu_name, int *count)
 	if (!pmu)
 		return -1;
 
-	if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, pmu_name) != 0) {
+	if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, pmu_name, /*from_env=*/false) != 0) {
 		perf_pmu__delete(pmu);
 		return -1;
 	}
@@ -566,7 +566,7 @@ static int __test_uncore_pmu_event_aliases(struct perf_pmu_test_pmu *test_pmu)
 	if (!pmu)
 		return -1;
 
-	if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, test_pmu->pmu_name) != 0) {
+	if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, test_pmu->pmu_name, /*from_env=*/false) != 0) {
 		perf_pmu__delete(pmu);
 		return -1;
 	}
diff --git a/tools/perf/util/drm_pmu.c b/tools/perf/util/drm_pmu.c
index 5e4bdd8e7fa8..7ef4126ee7e9 100644
--- a/tools/perf/util/drm_pmu.c
+++ b/tools/perf/util/drm_pmu.c
@@ -115,7 +115,7 @@ static struct drm_pmu *add_drm_pmu(struct list_head *pmus, char *line, size_t li
 	if (!drm)
 		return NULL;
 
-	if (perf_pmu__init(&drm->pmu, type, name) != 0) {
+	if (perf_pmu__init(&drm->pmu, type, name, /*from_env=*/false) != 0) {
 		perf_pmu__delete(&drm->pmu);
 		return NULL;
 	}
@@ -515,10 +515,12 @@ int evsel__drm_pmu_open(struct evsel *evsel,
 			struct perf_thread_map *threads,
 			int start_cpu_map_idx, int end_cpu_map_idx)
 {
-	(void)evsel;
 	(void)threads;
 	(void)start_cpu_map_idx;
 	(void)end_cpu_map_idx;
+	/* PMUs reconstructed from a perf.data file can't be opened. */
+	if (!evsel->pmu || evsel->pmu->from_env)
+		return -EINVAL;
 	return 0;
 }
 
@@ -657,11 +659,17 @@ static uint64_t drm_pmu__read_for_pid(struct drm_pmu *drm, struct evsel *evsel,
 
 int evsel__drm_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread)
 {
-	struct drm_pmu *drm = container_of(evsel->pmu, struct drm_pmu, pmu);
+	struct drm_pmu *drm;
 	struct perf_counts_values *count, *old_count = NULL;
-	int pid = perf_thread_map__pid(evsel->core.threads, thread);
+	int pid;
 	uint64_t counter;
 
+	if (!evsel->pmu || evsel->pmu->from_env)
+		return -EINVAL;
+
+	drm = container_of(evsel->pmu, struct drm_pmu, pmu);
+	pid = perf_thread_map__pid(evsel->core.threads, thread);
+
 	if (pid != -1)
 		counter = drm_pmu__read_for_pid(drm, evsel, pid);
 	else
diff --git a/tools/perf/util/hwmon_pmu.c b/tools/perf/util/hwmon_pmu.c
index 96aeb379e169..1526afa37e87 100644
--- a/tools/perf/util/hwmon_pmu.c
+++ b/tools/perf/util/hwmon_pmu.c
@@ -365,7 +365,7 @@ struct perf_pmu *hwmon_pmu__new(struct list_head *pmus, const char *hwmon_dir,
 	if (!hwm)
 		return NULL;
 
-	if (perf_pmu__init(&hwm->pmu, type, buf) != 0) {
+	if (perf_pmu__init(&hwm->pmu, type, buf, /*from_env=*/false) != 0) {
 		perf_pmu__delete(&hwm->pmu);
 		return NULL;
 	}
@@ -763,12 +763,18 @@ int evsel__hwmon_pmu_open(struct evsel *evsel,
 			  struct perf_thread_map *threads,
 			  int start_cpu_map_idx, int end_cpu_map_idx)
 {
-	struct hwmon_pmu *hwm = container_of(evsel->pmu, struct hwmon_pmu, pmu);
+	struct hwmon_pmu *hwm;
 	union hwmon_pmu_event_key key = {
 		.type_and_num = evsel->core.attr.config,
 	};
 	int idx = 0, thread = 0, nthreads, err = 0;
-	int dir = open(hwm->hwmon_dir, O_CLOEXEC | O_DIRECTORY | O_RDONLY);
+	int dir;
+
+	if (!evsel->pmu || evsel->pmu->from_env)
+		return -EINVAL;
+
+	hwm = container_of(evsel->pmu, struct hwmon_pmu, pmu);
+	dir = open(hwm->hwmon_dir, O_CLOEXEC | O_DIRECTORY | O_RDONLY);
 
 	if (dir < 0)
 		return -errno;
@@ -815,6 +821,10 @@ int evsel__hwmon_pmu_read(struct evsel *evsel, int cpu_map_idx, int thread)
 	ssize_t len;
 	struct perf_counts_values *count, *old_count = NULL;
 
+	/* PMUs reconstructed from a perf.data file can't be read. */
+	if (!evsel->pmu || evsel->pmu->from_env)
+		return -EINVAL;
+
 	if (evsel->prev_raw_counts)
 		old_count = perf_counts(evsel->prev_raw_counts, cpu_map_idx, thread);
 
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 01753d5c69d1..531641e66ef4 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -1188,9 +1188,10 @@ static bool aliases__equal(long key1, long key2, void *ctx __maybe_unused)
 	return strcasecmp((const char *)key1, (const char *)key2) == 0;
 }
 
-int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name)
+int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name, bool from_env)
 {
 	pmu->type = type;
+	pmu->from_env = from_env;
 	INIT_LIST_HEAD(&pmu->format);
 	INIT_LIST_HEAD(&pmu->caps);
 
@@ -1240,7 +1241,7 @@ struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char
 	if (!pmu)
 		return NULL;
 
-	if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, name) != 0) {
+	if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, name, /*from_env=*/false) != 0) {
 		perf_pmu__delete(pmu);
 		return NULL;
 	}
@@ -1695,9 +1696,9 @@ int perf_pmu__config_terms(const struct perf_pmu *pmu,
 	struct parse_events_term *term;
 
 	if (perf_pmu__is_hwmon(pmu))
-		return hwmon_pmu__config_terms(pmu, attr, terms, err);
+		return pmu->from_env ? -EINVAL : hwmon_pmu__config_terms(pmu, attr, terms, err);
 	if (perf_pmu__is_drm(pmu))
-		return drm_pmu__config_terms(pmu, attr, terms, err);
+		return pmu->from_env ? -EINVAL : drm_pmu__config_terms(pmu, attr, terms, err);
 
 	list_for_each_entry(term, &terms->terms, list) {
 		if (pmu_config_term(pmu, attr, term, terms, zero, apply_hardcoded, err))
@@ -1857,11 +1858,11 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct parse_events_terms *head_
 	info->retirement_latency_max = 0.0;
 
 	if (perf_pmu__is_hwmon(pmu)) {
-		ret = hwmon_pmu__check_alias(head_terms, info, err);
+		ret = pmu->from_env ? 0 : hwmon_pmu__check_alias(head_terms, info, err);
 		goto out;
 	}
 	if (perf_pmu__is_drm(pmu)) {
-		ret = drm_pmu__check_alias(pmu, head_terms, info, err);
+		ret = pmu->from_env ? 0 : drm_pmu__check_alias(pmu, head_terms, info, err);
 		goto out;
 	}
 
@@ -2118,11 +2119,11 @@ bool perf_pmu__have_event(struct perf_pmu *pmu, const char *name)
 	if (perf_pmu__is_tool(pmu) && tool_pmu__skip_event(name))
 		return false;
 	if (perf_pmu__is_tracepoint(pmu))
-		return tp_pmu__have_event(pmu, name);
+		return pmu->from_env ? false : tp_pmu__have_event(pmu, name);
 	if (perf_pmu__is_hwmon(pmu))
-		return hwmon_pmu__have_event(pmu, name);
+		return pmu->from_env ? false : hwmon_pmu__have_event(pmu, name);
 	if (perf_pmu__is_drm(pmu))
-		return drm_pmu__have_event(pmu, name);
+		return pmu->from_env ? false : drm_pmu__have_event(pmu, name);
 	if (perf_pmu__find_alias(pmu, name, /*load=*/ true) != NULL)
 		return true;
 	if (pmu->cpu_aliases_added || (!pmu->events_table && !pmu->is_core))
@@ -2139,11 +2140,11 @@ size_t perf_pmu__num_events(struct perf_pmu *pmu)
 	size_t nr;
 
 	if (perf_pmu__is_tracepoint(pmu))
-		return tp_pmu__num_events(pmu);
+		return pmu->from_env ? 0 : tp_pmu__num_events(pmu);
 	if (perf_pmu__is_hwmon(pmu))
-		return hwmon_pmu__num_events(pmu);
+		return pmu->from_env ? 0 : hwmon_pmu__num_events(pmu);
 	if (perf_pmu__is_drm(pmu))
-		return drm_pmu__num_events(pmu);
+		return pmu->from_env ? 0 : drm_pmu__num_events(pmu);
 
 	pmu_aliases_parse(pmu);
 	nr = pmu->sysfs_aliases + pmu->sys_json_aliases;
@@ -2275,11 +2276,11 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus,
 	size_t size_rem;
 
 	if (perf_pmu__is_tracepoint(pmu))
-		return tp_pmu__for_each_event(pmu, state, cb);
+		return pmu->from_env ? 0 : tp_pmu__for_each_event(pmu, state, cb);
 	if (perf_pmu__is_hwmon(pmu))
-		return hwmon_pmu__for_each_event(pmu, state, cb);
+		return pmu->from_env ? 0 : hwmon_pmu__for_each_event(pmu, state, cb);
 	if (perf_pmu__is_drm(pmu))
-		return drm_pmu__for_each_event(pmu, state, cb);
+		return pmu->from_env ? 0 : drm_pmu__for_each_event(pmu, state, cb);
 
 	pmu_aliases_parse(pmu);
 	pmu_add_cpu_aliases(pmu);
@@ -2784,10 +2785,12 @@ void perf_pmu__delete(struct perf_pmu *pmu)
 	if (!pmu)
 		return;
 
-	if (perf_pmu__is_hwmon(pmu))
-		hwmon_pmu__exit(pmu);
-	else if (perf_pmu__is_drm(pmu))
-		drm_pmu__exit(pmu);
+	if (!pmu->from_env) {
+		if (perf_pmu__is_hwmon(pmu))
+			hwmon_pmu__exit(pmu);
+		else if (perf_pmu__is_drm(pmu))
+			drm_pmu__exit(pmu);
+	}
 
 	perf_pmu__del_formats(&pmu->format);
 	perf_pmu__del_aliases(pmu);
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 2c8916ad073e..ea55882ce670 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -161,6 +161,8 @@ struct perf_pmu {
 	uint32_t cpu_common_json_aliases;
 	/** @sysfs_aliases_loaded: Are sysfs aliases loaded from disk? */
 	bool sysfs_aliases_loaded;
+	/** @from_env: Was the PMU reconstructed from a perf_env header? */
+	bool from_env;
 	/**
 	 * @cpu_aliases_added: Have all json events table entries for the PMU
 	 * been added?
@@ -345,7 +347,16 @@ int perf_pmu__pathname_scnprintf(char *buf, size_t size,
 int perf_pmu__event_source_devices_fd(void);
 int perf_pmu__pathname_fd(int dirfd, const char *pmu_name, const char *filename, int flags);
 
-int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name);
+/**
+ * perf_pmu__init() - Initialize a pre-allocated struct perf_pmu.
+ * @pmu: The PMU structure to initialize.
+ * @type: The PMU type identifier (PERF_TYPE_* or dynamic sysfs type).
+ * @name: The PMU name string (copied via strdup).
+ * @from_env: True if reconstructed from a perf_env header rather than live sysfs.
+ *
+ * Return: 0 on success, -ENOMEM if duplicating @name fails.
+ */
+int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name, bool from_env);
 struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *lookup_name,
 				  bool eager_load);
 struct perf_pmu *perf_pmu__create_placeholder_core_pmu(struct list_head *core_pmus);
diff --git a/tools/perf/util/tool_pmu.c b/tools/perf/util/tool_pmu.c
index d2f7591747d9..6054d5d5aece 100644
--- a/tools/perf/util/tool_pmu.c
+++ b/tools/perf/util/tool_pmu.c
@@ -714,7 +714,7 @@ struct perf_pmu *tool_pmu__new(void)
 	if (!tool)
 		return NULL;
 
-	if (perf_pmu__init(tool, PERF_PMU_TYPE_TOOL, "tool") != 0) {
+	if (perf_pmu__init(tool, PERF_PMU_TYPE_TOOL, "tool", /*from_env=*/false) != 0) {
 		perf_pmu__delete(tool);
 		return NULL;
 	}
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 08/30] perf pmus: Introduce struct perf_pmus and perf_env__pmus()
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (6 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 07/30] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:36   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 09/30] perf pmus: Pass struct perf_pmus to find helpers Ian Rogers
                   ` (21 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Move the static global core_pmus, other_pmus, read_pmu_types,
num_core_pmus, and extended_type state from pmus.c into a new
struct perf_pmus embedded in struct perf_env and accessed via
perf_env__pmus().

Rename perf_pmus__destroy() to perf_pmus__exit() to match the
perf_env__init()/perf_env__exit() naming convention, and update it
along with perf_pmus__find_by_attr(), perf_pmus__scan_for_event(),
perf_pmus__scan_matching_wildcard(), perf_pmus__scan_for_uncore_id(),
perf_pmus__pmu_for_pmu_filter(), perf_pmus__print_pmu_events(),
perf_pmus__print_raw_pmu_events(), perf_pmus__have_event(),
perf_pmus__num_core_pmus(), perf_pmus__supports_extended_type(),
perf_pmus__add_test_pmu(), and perf_pmus__add_test_hwmon_pmu() to take
struct perf_pmus *.

To keep this commit reviewable, the high-fanout perf_pmus__find(),
perf_pmus__find_by_type(), perf_pmus__find_core_pmu(),
perf_pmus__scan(), and perf_pmus__scan_core() helpers are updated to
take struct perf_pmus * in the next two commits.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/arch/x86/tests/hybrid.c            |   3 +-
 tools/perf/bench/pmu-scan.c                   |   7 +-
 tools/perf/builtin-list.c                     |  19 +-
 tools/perf/builtin-record.c                   |   2 +-
 tools/perf/builtin-stat.c                     |   2 +-
 tools/perf/tests/hwmon_pmu.c                  |   4 +-
 tools/perf/tests/mmap-basic.c                 |   4 +-
 tools/perf/tests/parse-events.c               |  15 +-
 tools/perf/tests/pmu.c                        |   3 +-
 tools/perf/util/cputopo.c                     |   3 +-
 tools/perf/util/env.c                         |  12 +-
 tools/perf/util/env.h                         |  10 +
 tools/perf/util/evlist.c                      |  13 +-
 tools/perf/util/evlist.h                      |   2 +
 tools/perf/util/evsel.c                       |  14 +-
 .../util/kvm-stat-arch/kvm-stat-powerpc.c     |   3 +-
 tools/perf/util/metricgroup.c                 |   6 +-
 tools/perf/util/parse-events.c                |  44 ++-
 tools/perf/util/parse-events.h                |   3 +
 .../perf/util/perf-regs-arch/perf_regs_x86.c  |   7 +-
 tools/perf/util/pmu.c                         |   9 +-
 tools/perf/util/pmu.h                         |   3 +
 tools/perf/util/pmus.c                        | 343 +++++++++++-------
 tools/perf/util/pmus.h                        | 205 ++++++++++-
 tools/perf/util/print-events.c                |   7 +-
 tools/perf/util/session.c                     |  11 +
 tools/perf/util/stat-display.c                |   4 +-
 tools/perf/util/stat-shadow.c                 |   3 +-
 28 files changed, 560 insertions(+), 201 deletions(-)

diff --git a/tools/perf/arch/x86/tests/hybrid.c b/tools/perf/arch/x86/tests/hybrid.c
index 0477e17b8e53..011217998a3e 100644
--- a/tools/perf/arch/x86/tests/hybrid.c
+++ b/tools/perf/arch/x86/tests/hybrid.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 #include "arch-tests.h"
 #include "debug.h"
+#include "env.h"
 #include "evlist.h"
 #include "evsel.h"
 #include "pmu.h"
@@ -303,7 +304,7 @@ static int test_events(const struct evlist_test *events, int cnt)
 
 int test__hybrid(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
 {
-	if (perf_pmus__num_core_pmus() == 1)
+	if (perf_pmus__num_core_pmus(perf_env__pmus(host_env())) == 1)
 		return TEST_SKIP;
 
 	return test_events(test__hybrid_events, ARRAY_SIZE(test__hybrid_events));
diff --git a/tools/perf/bench/pmu-scan.c b/tools/perf/bench/pmu-scan.c
index 14a464ad8cea..1d7dd4b203e0 100644
--- a/tools/perf/bench/pmu-scan.c
+++ b/tools/perf/bench/pmu-scan.c
@@ -8,6 +8,7 @@
 #include <stdio.h>
 #include "bench.h"
 #include "util/debug.h"
+#include "util/env.h"
 #include "util/pmu.h"
 #include "util/pmus.h"
 #include "util/stat.h"
@@ -42,6 +43,7 @@ static struct pmu_scan_result *results;
 
 static int save_result(void)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 	struct list_head *list;
 	struct pmu_scan_result *r;
@@ -69,7 +71,7 @@ static int save_result(void)
 		nr_pmus++;
 	}
 
-	perf_pmus__destroy();
+	perf_pmus__exit(pmus);
 	return 0;
 }
 
@@ -128,6 +130,7 @@ static void delete_result(void)
 
 static int run_pmu_scan(void)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct stats stats;
 	struct timeval start, end, diff;
 	double time_average, time_stddev;
@@ -158,7 +161,7 @@ static int run_pmu_scan(void)
 			update_stats(&stats, runtime_us);
 
 			ret = check_result(core_only);
-			perf_pmus__destroy();
+			perf_pmus__exit(pmus);
 			if (ret < 0)
 				break;
 		}
diff --git a/tools/perf/builtin-list.c b/tools/perf/builtin-list.c
index 47668d66c395..573d13baa7fd 100644
--- a/tools/perf/builtin-list.c
+++ b/tools/perf/builtin-list.c
@@ -11,6 +11,7 @@
 #include "builtin.h"
 
 #include "util/print-events.h"
+#include "util/env.h"
 #include "util/pmus.h"
 #include "util/pmu.h"
 #include "util/debug.h"
@@ -567,6 +568,7 @@ static int list__tui(int argc, const char **argv)
 
 int cmd_list(int argc, const char **argv)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	int i, ret = 0;
 	struct print_state default_ps = {
 		.fp = stdout,
@@ -660,7 +662,8 @@ int cmd_list(int argc, const char **argv)
 		if (unit_name)
 			ps->pmu_glob = strdup(unit_name);
 		else if (cputype) {
-			const struct perf_pmu *pmu = perf_pmus__pmu_for_pmu_filter(cputype);
+			const struct perf_pmu *pmu =
+				perf_pmus__pmu_for_pmu_filter(pmus, cputype);
 
 			if (!pmu) {
 				pr_err("ERROR: cputype is not supported!\n");
@@ -692,7 +695,7 @@ int cmd_list(int argc, const char **argv)
 				ret = -1;
 				goto out;
 			}
-			perf_pmus__print_pmu_events(&print_cb, ps);
+			perf_pmus__print_pmu_events(pmus, &print_cb, ps);
 			zfree(&default_ps.pmu_glob);
 			default_ps.pmu_glob = old_pmu_glob;
 		} else if (strcmp(argv[i], "hw") == 0 ||
@@ -704,7 +707,7 @@ int cmd_list(int argc, const char **argv)
 				ret = -1;
 				goto out;
 			}
-			perf_pmus__print_pmu_events(&print_cb, ps);
+			perf_pmus__print_pmu_events(pmus, &print_cb, ps);
 			zfree(&ps->event_glob);
 			ps->event_glob = old_event_glob;
 		} else if (strcmp(argv[i], "sw") == 0 ||
@@ -718,7 +721,7 @@ int cmd_list(int argc, const char **argv)
 					ret = -1;
 					goto out;
 				}
-				perf_pmus__print_pmu_events(&print_cb, ps);
+				perf_pmus__print_pmu_events(pmus, &print_cb, ps);
 				zfree(&ps->pmu_glob);
 			}
 			ps->pmu_glob = old_pmu_glob;
@@ -731,12 +734,12 @@ int cmd_list(int argc, const char **argv)
 				ret = -1;
 				goto out;
 			}
-			perf_pmus__print_pmu_events(&print_cb, ps);
+			perf_pmus__print_pmu_events(pmus, &print_cb, ps);
 			zfree(&ps->event_glob);
 			ps->event_glob = old_event_glob;
 		} else if (strcmp(argv[i], "pmu") == 0) {
 			ps->exclude_abi = true;
-			perf_pmus__print_pmu_events(&print_cb, ps);
+			perf_pmus__print_pmu_events(pmus, &print_cb, ps);
 			ps->exclude_abi = false;
 		} else if (strcmp(argv[i], "sdt") == 0)
 			print_sdt_events(&print_cb, ps);
@@ -770,7 +773,7 @@ int cmd_list(int argc, const char **argv)
 				ret = -1;
 				goto out;
 			}
-			perf_pmus__print_pmu_events(&print_cb, ps);
+			perf_pmus__print_pmu_events(pmus, &print_cb, ps);
 			zfree(&ps->pmu_glob);
 			ps->pmu_glob = old_pmu_glob;
 			print_sdt_events(&print_cb, ps);
@@ -785,7 +788,7 @@ int cmd_list(int argc, const char **argv)
 				continue;
 			}
 			ps->event_glob = s;
-			perf_pmus__print_pmu_events(&print_cb, ps);
+			perf_pmus__print_pmu_events(pmus, &print_cb, ps);
 			print_sdt_events(&print_cb, ps);
 			ps->metrics = true;
 			ps->metricgroups = true;
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index ad81458989b5..7de04919d194 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -936,7 +936,7 @@ static int record__config_tracking_events(struct record *rec)
 	 * delay of waiting or event synthesis.
 	 */
 	if (opts->target.initial_delay || target__has_cpu(&opts->target) ||
-	    perf_pmus__num_core_pmus() > 1) {
+	    perf_pmus__num_core_pmus(perf_env__pmus(host_env())) > 1) {
 		/*
 		 * User space tasks can migrate between CPUs, so when tracing
 		 * selected CPUs, sideband for all CPUs is still needed.
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index 37752d6bfef4..8706798290eb 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -1209,7 +1209,7 @@ static int parse_cputype(const struct option *opt,
 		return -1;
 	}
 
-	pmu = perf_pmus__pmu_for_pmu_filter(str);
+	pmu = perf_pmus__pmu_for_pmu_filter(perf_env__pmus(host_env()), str);
 	if (!pmu) {
 		fprintf(stderr, "--cputype %s is not supported!\n", str);
 		return -1;
diff --git a/tools/perf/tests/hwmon_pmu.c b/tools/perf/tests/hwmon_pmu.c
index e26b3fe3fab1..8f3d09d3f2a1 100644
--- a/tools/perf/tests/hwmon_pmu.c
+++ b/tools/perf/tests/hwmon_pmu.c
@@ -11,6 +11,7 @@
 #include <sys/stat.h>
 
 #include "debug.h"
+#include "env.h"
 #include "evlist.h"
 #include "parse-events.h"
 #include "pmus.h"
@@ -137,7 +138,8 @@ static struct perf_pmu *test_pmu_get(char *dir, size_t sz)
 	}
 
 	/* Make the PMU reading the files created above. */
-	hwm = perf_pmus__add_test_hwmon_pmu(dir, "hwmon1234", test_hwmon_name);
+	hwm = perf_pmus__add_test_hwmon_pmu(perf_env__pmus(host_env()),
+					    dir, "hwmon1234", test_hwmon_name);
 	if (!hwm)
 		pr_err("Test hwmon creation failed\n");
 
diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c
index 602a78d48b10..dada4af5fb68 100644
--- a/tools/perf/tests/mmap-basic.c
+++ b/tools/perf/tests/mmap-basic.c
@@ -17,6 +17,7 @@
 
 #include "cpumap.h"
 #include "debug.h"
+#include "env.h"
 #include "event.h"
 #include "evlist.h"
 #include "evsel.h"
@@ -264,6 +265,7 @@ static enum user_read_state set_user_read(struct perf_pmu *pmu,
 
 static int test_stat_user_read(u64 event, enum user_read_state enabled)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 	struct perf_thread_map *threads = perf_thread_map__new_dummy();
 	int ret = TEST_OK;
@@ -279,7 +281,7 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled)
 		enum user_read_state saved_user_read_state = set_user_read(pmu, enabled);
 		struct perf_event_attr attr = {
 			.type	= PERF_TYPE_HARDWARE,
-			.config	= perf_pmus__supports_extended_type()
+			.config	= perf_pmus__supports_extended_type(pmus)
 			? event | ((u64)pmu->type << PERF_PMU_TYPE_SHIFT)
 				: event,
 		};
diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c
index 5f6f95c8a5b4..8969bcf004ea 100644
--- a/tools/perf/tests/parse-events.c
+++ b/tools/perf/tests/parse-events.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 #include "parse-events.h"
+#include "env.h"
 #include "evsel.h"
 #include "evsel_fprintf.h"
 #include "evlist.h"
@@ -68,7 +69,7 @@ static int num_core_entries(struct evlist *evlist)
 		if (!evsel->pmu->is_core)
 			continue;
 		if (core_pmu != evsel->pmu && core_pmu != NULL)
-			return perf_pmus__num_core_pmus();
+			return perf_pmus__num_core_pmus(perf_env__pmus(host_env()));
 		core_pmu = evsel->pmu;
 	}
 	return 1;
@@ -1022,7 +1023,8 @@ static int test__group3(struct evlist *evlist __maybe_unused)
 	struct evsel *evsel, *group1_leader = NULL, *group2_leader = NULL;
 
 	TEST_ASSERT_EVLIST("wrong number of entries",
-			   evlist__nr_entries(evlist) == (3 * perf_pmus__num_core_pmus() + 2),
+			   evlist__nr_entries(evlist) ==
+			   (3 * perf_pmus__num_core_pmus(perf_env__pmus(host_env())) + 2),
 			   evlist);
 	/*
 	 * Currently the software event won't be grouped with the hardware event
@@ -1582,6 +1584,7 @@ static int test__checkevent_pinned_modifier(struct evlist *evlist)
 
 static int test__pinned_group(struct evlist *evlist)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct evsel *evsel = NULL, *leader;
 
 	TEST_ASSERT_EVLIST("wrong number of entries",
@@ -1597,7 +1600,7 @@ static int test__pinned_group(struct evlist *evlist)
 		TEST_ASSERT_EVSEL("wrong group name", !evsel->group_name, evsel);
 		TEST_ASSERT_EVSEL("wrong leader", evsel__has_leader(evsel, leader), evsel);
 		/* TODO: The group modifier is not copied to the split group leader. */
-		if (perf_pmus__num_core_pmus() == 1)
+		if (perf_pmus__num_core_pmus(pmus) == 1)
 			TEST_ASSERT_EVSEL("wrong pinned", evsel->core.attr.pinned, evsel);
 
 		/* cache-misses - can not be pinned, but will go on with the leader */
@@ -1635,6 +1638,7 @@ static int test__checkevent_exclusive_modifier(struct evlist *evlist)
 
 static int test__exclusive_group(struct evlist *evlist)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct evsel *evsel = NULL, *leader;
 
 	TEST_ASSERT_EVLIST("wrong number of entries",
@@ -1650,7 +1654,7 @@ static int test__exclusive_group(struct evlist *evlist)
 		TEST_ASSERT_EVSEL("wrong group name", !evsel->group_name, evsel);
 		TEST_ASSERT_EVSEL("wrong leader", evsel__has_leader(evsel, leader), evsel);
 		/* TODO: The group modifier is not copied to the split group leader. */
-		if (perf_pmus__num_core_pmus() == 1)
+		if (perf_pmus__num_core_pmus(pmus) == 1)
 			TEST_ASSERT_EVSEL("wrong exclusive", evsel->core.attr.exclusive, evsel);
 
 		/* cache-misses - can not be pinned, but will go on with the leader */
@@ -1803,7 +1807,8 @@ static int test__ratio_to_prev(struct evlist *evlist)
 	struct evsel *evsel, *leader;
 
 	TEST_ASSERT_VAL("wrong number of entries",
-			2 * perf_pmus__num_core_pmus() == evlist__nr_entries(evlist));
+			2 * perf_pmus__num_core_pmus(perf_env__pmus(host_env())) ==
+			evlist__nr_entries(evlist));
 
 	evlist__for_each_entry(evlist, evsel) {
 		if (evsel != evsel__leader(evsel) ||
diff --git a/tools/perf/tests/pmu.c b/tools/perf/tests/pmu.c
index f208932590df..fc731857ec71 100644
--- a/tools/perf/tests/pmu.c
+++ b/tools/perf/tests/pmu.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 #include "evlist.h"
+#include "env.h"
 #include "evsel.h"
 #include "parse-events.h"
 #include "pmu.h"
@@ -143,7 +144,7 @@ static struct perf_pmu *test_pmu_get(char *dir, size_t sz)
 	close(file);
 
 	/* Make the PMU reading the files created above. */
-	pmu = perf_pmus__add_test_pmu(dirfd, "perf-pmu-test");
+	pmu = perf_pmus__add_test_pmu(perf_env__pmus(host_env()), dirfd, "perf-pmu-test");
 	if (!pmu)
 		pr_err("Test PMU creation failed\n");
 
diff --git a/tools/perf/util/cputopo.c b/tools/perf/util/cputopo.c
index e0091804fe98..04ab4d010149 100644
--- a/tools/perf/util/cputopo.c
+++ b/tools/perf/util/cputopo.c
@@ -470,9 +470,10 @@ static int load_hybrid_node(struct hybrid_topology_node *node,
 
 struct hybrid_topology *hybrid_topology__new(void)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 	struct hybrid_topology *tp = NULL;
-	int nr = perf_pmus__num_core_pmus(), i = 0;
+	int nr = perf_pmus__num_core_pmus(pmus), i = 0;
 
 	if (nr <= 1)
 		return NULL;
diff --git a/tools/perf/util/env.c b/tools/perf/util/env.c
index 1fe6ce84e607..5f16920f0a89 100644
--- a/tools/perf/util/env.c
+++ b/tools/perf/util/env.c
@@ -261,6 +261,7 @@ static struct perf_env __host_env = {
 	},
 	.kernel_is_64_bit = -1,
 	.lock = { .lock = __PERF_MUTEX_INITIALIZER },
+	.pmus = PERF_PMUS_INIT(__host_env.pmus),
 };
 
 void perf_env__exit(struct perf_env *env)
@@ -316,6 +317,8 @@ void perf_env__exit(struct perf_env *env)
 	zfree(&env->pmu_caps);
 	zfree(&env->memory_ranges);
 	free_cpu_domain_info(env->cpu_domain, env->schedstat_version, env->nr_cpus_avail);
+	perf_pmus__exit(&env->pmus);
+	mutex_destroy(&env->pmus.lock);
 }
 
 void perf_env__init(struct perf_env *env)
@@ -328,6 +331,7 @@ void perf_env__init(struct perf_env *env)
 #endif
 	env->kernel_is_64_bit = -1;
 	mutex_init(&env->lock);
+	perf_pmus__init(&env->pmus);
 }
 
 struct perf_env *host_env(void)
@@ -335,6 +339,11 @@ struct perf_env *host_env(void)
 	return &__host_env;
 }
 
+struct perf_pmus *perf_env__pmus(struct perf_env *env)
+{
+	return &env->pmus;
+}
+
 static const char *uts_machine(void)
 {
 	static struct utsname uts;
@@ -595,12 +604,13 @@ static int __perf_env__read_core_pmu_caps(const struct perf_pmu *pmu,
 
 int perf_env__read_core_pmu_caps(struct perf_env *env)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct pmu_caps *pmu_caps;
 	struct perf_pmu *pmu = NULL;
 	int nr_pmu, i = 0, j;
 	int ret;
 
-	nr_pmu = perf_pmus__num_core_pmus();
+	nr_pmu = perf_pmus__num_core_pmus(pmus);
 
 	if (!nr_pmu)
 		return -ENODEV;
diff --git a/tools/perf/util/env.h b/tools/perf/util/env.h
index 134102148a5c..e38e953060c9 100644
--- a/tools/perf/util/env.h
+++ b/tools/perf/util/env.h
@@ -5,6 +5,7 @@
 #include <linux/types.h>
 #include <linux/rbtree.h>
 #include "cpumap.h"
+#include "pmus.h"
 #include "rwsem.h"
 #include "mutex.h"
 
@@ -169,6 +170,7 @@ struct perf_env {
 	} clock;
 	/* Protects lazy environment initialization (e.g. os_release, e_machine). */
 	struct mutex		lock;
+	struct perf_pmus	pmus;
 };
 
 enum perf_compress_type {
@@ -231,6 +233,14 @@ void perf_env__init(struct perf_env *env);
  * Return: Pointer to the live host's struct perf_env.
  */
 struct perf_env *host_env(void);
+
+/**
+ * perf_env__pmus() - Return the struct perf_pmus associated with @env.
+ * @env: Environment whose PMU collection to retrieve.
+ *
+ * Return: Pointer to the struct perf_pmus for @env.
+ */
+struct perf_pmus *perf_env__pmus(struct perf_env *env);
 #ifdef HAVE_LIBBPF_SUPPORT
 bool __perf_env__insert_bpf_prog_info(struct perf_env *env,
 				      struct bpf_prog_info_node *info_node);
diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
index c2402e4791b6..4e641ccc176f 100644
--- a/tools/perf/util/evlist.c
+++ b/tools/perf/util/evlist.c
@@ -64,6 +64,7 @@
 #include "pmus.h"
 #include "record.h"
 #include "sample.h"
+#include "session.h"
 #include "stat.h"
 #include "strbuf.h"
 #include "string2.h"
@@ -2941,11 +2942,14 @@ static bool evlist__disable_uniquify(struct evlist *evlist)
 			return false;
 		first = false;
 		if (counter->pmu) {
+			struct perf_pmus *pmus =
+				counter->pmu->pmus ?: perf_env__pmus(host_env());
+
 			/* Allow uniquify for uncore PMUs. */
 			if (!counter->pmu->is_core)
 				return false;
 			/* Keep hybrid event names uniquified for clarity. */
-			if (perf_pmus__num_core_pmus() > 1)
+			if (perf_pmus__num_core_pmus(pmus) > 1)
 				return false;
 		}
 		last_pmu = counter->pmu;
@@ -3006,3 +3010,10 @@ bool evlist__needs_bpf_sb_event(struct evlist *evlist)
 
 	return false;
 }
+
+struct perf_env *evlist__env(struct evlist *evlist)
+{
+	struct perf_session *session = evlist ? evlist__session(evlist) : NULL;
+
+	return session ? perf_session__env(session) : NULL;
+}
diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h
index 0f380421e03d..fc32979e08b8 100644
--- a/tools/perf/util/evlist.h
+++ b/tools/perf/util/evlist.h
@@ -23,6 +23,7 @@
 #include "rblist.h"
 
 struct perf_cpu_map;
+struct perf_env;
 struct perf_stat_config;
 struct pollfd;
 struct record_opts;
@@ -684,5 +685,6 @@ void evlist__warn_user_requested_cpus(struct evlist *evlist, const char *cpu_lis
 void evlist__uniquify_evsel_names(struct evlist *evlist, const struct perf_stat_config *config);
 bool evlist__has_bpf_output(struct evlist *evlist);
 bool evlist__needs_bpf_sb_event(struct evlist *evlist);
+struct perf_env *evlist__env(struct evlist *evlist);
 
 #endif /* __PERF_EVLIST_H */
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 6d69f63b7fb1..2fa8a0036375 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -2290,9 +2290,12 @@ bool __evsel__match(const struct evsel *evsel, u32 type, u64 config)
 		return true;
 	if (type != PERF_TYPE_HARDWARE && type != PERF_TYPE_HW_CACHE)
 		return false;
-	if ((e_type == PERF_TYPE_HARDWARE || e_type == PERF_TYPE_HW_CACHE) &&
-		perf_pmus__supports_extended_type())
-		e_config &= PERF_HW_EVENT_MASK;
+	if (e_type == PERF_TYPE_HARDWARE || e_type == PERF_TYPE_HW_CACHE) {
+		struct perf_env *env = evsel__env((struct evsel *)evsel) ?: host_env();
+
+		if (perf_pmus__supports_extended_type(perf_env__pmus(env)))
+			e_config &= PERF_HW_EVENT_MASK;
+	}
 	if (e_type == type && e_config == config)
 		return true;
 	if (type == PERF_TYPE_HARDWARE && evsel->pmu && evsel->pmu->is_core &&
@@ -4779,10 +4782,13 @@ void evsel__zero_per_pkg(struct evsel *evsel)
  */
 bool evsel__is_hybrid(const struct evsel *evsel)
 {
+	struct perf_env *env;
+
 	if (!evsel->core.is_pmu_core)
 		return false;
 
-	return perf_pmus__num_core_pmus() > 1;
+	env = evsel__env((struct evsel *)evsel) ?: host_env();
+	return perf_pmus__num_core_pmus(perf_env__pmus(env)) > 1;
 }
 
 struct evsel *evsel__leader(const struct evsel *evsel)
diff --git a/tools/perf/util/kvm-stat-arch/kvm-stat-powerpc.c b/tools/perf/util/kvm-stat-arch/kvm-stat-powerpc.c
index 315b24f9a592..d3435174106b 100644
--- a/tools/perf/util/kvm-stat-arch/kvm-stat-powerpc.c
+++ b/tools/perf/util/kvm-stat-arch/kvm-stat-powerpc.c
@@ -3,6 +3,7 @@
 #include "../kvm-stat.h"
 #include "../parse-events.h"
 #include "../debug.h"
+#include "../env.h"
 #include "../evsel.h"
 #include "../evlist.h"
 #include "../pmus.h"
@@ -178,7 +179,7 @@ int __kvm_add_default_arch_event_powerpc(int *argc, const char **argv)
 {
 	int j = *argc;
 
-	if (!perf_pmus__have_event("trace_imc", "trace_cycles"))
+	if (!perf_pmus__have_event(perf_env__pmus(host_env()), "trace_imc", "trace_cycles"))
 		return -EINVAL;
 
 	argv[j++] = "-e";
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 98f3ba8f9533..63aa326944d7 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -297,8 +297,10 @@ static int setup_metric_events(const char *pmu, struct hashmap *ids,
 	const char *metric_id;
 	struct evsel *ev;
 	size_t ids_size, matched_events, i;
+	struct perf_pmus *pmus = perf_env__pmus(evlist__env(metric_evlist) ?: host_env());
 	bool all_pmus = !strcmp(pmu, "all") || !strcmp(pmu, "default_core") ||
-			perf_pmus__num_core_pmus() == 1 || !is_pmu_core(pmu);
+			perf_pmus__num_core_pmus(pmus) == 1 ||
+			!is_pmu_core(pmu);
 
 	*out_metric_events = NULL;
 	ids_size = hashmap__size(ids);
@@ -419,7 +421,7 @@ static int metricgroup__sys_event_iter(const struct pmu_metric *pm,
 		return 0;
 
 	/* Only process with the iterator if there is a a PMU that matches the ID. */
-	pmu = perf_pmus__scan_for_uncore_id(pmu, pm->compat);
+	pmu = perf_pmus__scan_for_uncore_id(perf_env__pmus(host_env()), pmu, pm->compat);
 	return pmu ? d->fn(pm, table, d->data) : 0;
 }
 
diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c
index 5ca6b93d8a2a..b56f0e2d1542 100644
--- a/tools/perf/util/parse-events.c
+++ b/tools/perf/util/parse-events.c
@@ -174,7 +174,8 @@ static struct evsel *
 __add_event(struct list_head *list, int *idx,
 	    struct perf_event_attr *attr,
 	    bool init_attr,
-	    const char *name, const char *metric_id, struct perf_pmu *pmu,
+	    const char *name, const char *metric_id,
+	    struct perf_pmus *pmus, struct perf_pmu *pmu,
 	    struct list_head *config_terms, struct evsel *first_wildcard_match,
 	    struct perf_cpu_map *user_cpus, u64 alternate_hw_config)
 {
@@ -224,7 +225,7 @@ __add_event(struct list_head *list, int *idx,
 	 * warnings won't be generated.
 	 */
 	if (!pmu)
-		pmu = perf_pmus__find_by_attr(attr);
+		pmu = perf_pmus__find_by_attr(pmus, attr);
 
 	if (pmu) {
 		is_pmu_core = pmu->is_core;
@@ -298,19 +299,21 @@ struct evsel *parse_events__add_event(int idx, struct perf_event_attr *attr,
 				      const char *name, const char *metric_id,
 				      struct perf_pmu *pmu)
 {
+	struct perf_pmus *pmus = pmu && pmu->pmus ? pmu->pmus : perf_env__pmus(host_env());
+
 	return __add_event(/*list=*/NULL, &idx, attr, /*init_attr=*/false, name,
-			   metric_id, pmu, /*config_terms=*/NULL,
+			   metric_id, pmus, pmu, /*config_terms=*/NULL,
 			   /*first_wildcard_match=*/NULL, /*cpu_list=*/NULL,
 			   /*alternate_hw_config=*/PERF_COUNT_HW_MAX);
 }
 
-static int add_event(struct list_head *list, int *idx,
+static int add_event(struct list_head *list, struct parse_events_state *parse_state,
 		     struct perf_event_attr *attr, const char *name,
 		     const char *metric_id, struct list_head *config_terms,
 		     u64 alternate_hw_config)
 {
-	return __add_event(list, idx, attr, /*init_attr*/true, name, metric_id,
-			   /*pmu=*/NULL, config_terms,
+	return __add_event(list, &parse_state->idx, attr, /*init_attr*/true, name, metric_id,
+			   parse_state->pmus, /*pmu=*/NULL, config_terms,
 			   /*first_wildcard_match=*/NULL, /*cpu_list=*/NULL,
 			   alternate_hw_config) ? 0 : -ENOMEM;
 }
@@ -415,7 +418,7 @@ int parse_events__decode_legacy_cache(const char *name, int extended_pmu_type, _
 		cache_result = PERF_COUNT_HW_CACHE_RESULT_ACCESS;
 
 	*config = cache_type | (cache_op << 8) | (cache_result << 16);
-	if (perf_pmus__supports_extended_type())
+	if (perf_pmus__host_supports_extended_type())
 		*config |= (__u64)extended_pmu_type << PERF_PMU_TYPE_SHIFT;
 	return 0;
 }
@@ -671,7 +674,7 @@ int parse_events_add_breakpoint(struct parse_events_state *parse_state,
 
 	name = get_config_name(head_config);
 
-	return add_event(list, &parse_state->idx, &attr, name, /*mertic_id=*/NULL,
+	return add_event(list, parse_state, &attr, name, /*mertic_id=*/NULL,
 			&config_terms, /*alternate_hw_config=*/PERF_COUNT_HW_MAX);
 }
 
@@ -1012,7 +1015,7 @@ static int config_term_pmu(struct perf_event_attr *attr,
 		if (!check_pmu_is_core(attr->type, term, parse_state->error))
 			return -EINVAL;
 		attr->config = term->val.num;
-		if (perf_pmus__supports_extended_type())
+		if (perf_pmus__supports_extended_type(parse_state->pmus))
 			attr->config |= (__u64)attr->type << PERF_PMU_TYPE_SHIFT;
 		attr->type = PERF_TYPE_HARDWARE;
 		return 0;
@@ -1037,7 +1040,7 @@ static int config_term_pmu(struct perf_event_attr *attr,
 		if (!check_pmu_is_core(attr->type, term, parse_state->error))
 			return -EINVAL;
 		attr->config = term->val.num;
-		if (perf_pmus__supports_extended_type())
+		if (perf_pmus__supports_extended_type(parse_state->pmus))
 			attr->config |= (__u64)attr->type << PERF_PMU_TYPE_SHIFT;
 		attr->type = PERF_TYPE_HW_CACHE;
 		return 0;
@@ -1412,7 +1415,7 @@ static int __parse_events_add_numeric(struct parse_events_state *parse_state,
 	attr.type = type;
 	attr.config = config;
 	if (extended_type && (type == PERF_TYPE_HARDWARE || type == PERF_TYPE_HW_CACHE)) {
-		assert(perf_pmus__supports_extended_type());
+		assert(perf_pmus__supports_extended_type(parse_state->pmus));
 		attr.config |= (u64)extended_type << PERF_PMU_TYPE_SHIFT;
 	}
 
@@ -1428,8 +1431,9 @@ static int __parse_events_add_numeric(struct parse_events_state *parse_state,
 	metric_id = get_config_metric_id(head_config);
 	cpus = get_config_cpu(head_config, parse_state->fake_pmu);
 	ret = __add_event(list, &parse_state->idx, &attr, /*init_attr*/true, name,
-			metric_id, pmu, &config_terms, first_wildcard_match,
-			cpus, /*alternate_hw_config=*/PERF_COUNT_HW_MAX) ? 0 : -ENOMEM;
+			metric_id, parse_state->pmus, pmu, &config_terms,
+			first_wildcard_match, cpus,
+			/*alternate_hw_config=*/PERF_COUNT_HW_MAX) ? 0 : -ENOMEM;
 	perf_cpu_map__put(cpus);
 	free_config_terms(&config_terms);
 	return ret;
@@ -1445,7 +1449,7 @@ int parse_events_add_numeric(struct parse_events_state *parse_state,
 	bool found_supported = false;
 
 	/* Wildcards on numeric values are only supported by core PMUs. */
-	if (wildcard && perf_pmus__supports_extended_type()) {
+	if (wildcard && perf_pmus__supports_extended_type(parse_state->pmus)) {
 		struct evsel *first_wildcard_match = NULL;
 		while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
 			int ret;
@@ -1524,7 +1528,7 @@ static int parse_events_add_pmu(struct parse_events_state *parse_state,
 	if (!const_parsed_terms || list_empty(&const_parsed_terms->terms)) {
 		evsel = __add_event(list, &parse_state->idx, &attr,
 				    /*init_attr=*/true, /*name=*/NULL,
-				    /*metric_id=*/NULL, pmu,
+				    /*metric_id=*/NULL, parse_state->pmus, pmu,
 				    /*config_terms=*/NULL, first_wildcard_match,
 				    /*cpu_list=*/NULL, alternate_hw_config);
 		return evsel ? 0 : -ENOMEM;
@@ -1591,7 +1595,7 @@ static int parse_events_add_pmu(struct parse_events_state *parse_state,
 	term_cpu = get_config_cpu(&parsed_terms, parse_state->fake_pmu);
 	evsel = __add_event(list, &parse_state->idx, &attr, /*init_attr=*/true,
 			    get_config_name(&parsed_terms),
-			    get_config_metric_id(&parsed_terms), pmu,
+			    get_config_metric_id(&parsed_terms), parse_state->pmus, pmu,
 			    &config_terms, first_wildcard_match, term_cpu, alternate_hw_config);
 	perf_cpu_map__put(term_cpu);
 	if (!evsel) {
@@ -1661,7 +1665,8 @@ int parse_events_multi_pmu_add(struct parse_events_state *parse_state,
 
 	INIT_LIST_HEAD(list);
 
-	while ((pmu = perf_pmus__scan_for_event(pmu, event_name)) != NULL) {
+	while ((pmu = perf_pmus__scan_for_event(parse_state->pmus,
+						pmu, event_name)) != NULL) {
 
 		if (parse_events__filter_pmu(parse_state, pmu))
 			continue;
@@ -1739,7 +1744,8 @@ int parse_events_multi_pmu_add_or_add_pmu(struct parse_events_state *parse_state
 
 	pmu = NULL;
 	/* Failed to add, try wildcard expansion of event_or_pmu as a PMU name. */
-	while ((pmu = perf_pmus__scan_matching_wildcard(pmu, event_or_pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan_matching_wildcard(parse_state->pmus,
+							pmu, event_or_pmu)) != NULL) {
 
 		if (parse_events__filter_pmu(parse_state, pmu))
 			continue;
@@ -1964,6 +1970,7 @@ int parse_events_terms(struct parse_events_terms *terms, const char *str)
 {
 	struct parse_events_state parse_state = {
 		.terms  = NULL,
+		.pmus   = perf_env__pmus(host_env()),
 		.stoken = PE_START_TERMS,
 	};
 	int ret;
@@ -2296,6 +2303,7 @@ int __parse_events(struct evlist *evlist, const char *str,
 		.list			= LIST_HEAD_INIT(parse_state.list),
 		.idx			= evlist__nr_entries(evlist),
 		.error			= err,
+		.pmus			= perf_env__pmus(evlist__env(evlist) ?: host_env()),
 		.stoken			= PE_START_EVENTS,
 		.fake_pmu		= fake_pmu,
 		.fake_tp		= fake_tp,
diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h
index b14c832b03a1..57eabdb97890 100644
--- a/tools/perf/util/parse-events.h
+++ b/tools/perf/util/parse-events.h
@@ -18,6 +18,7 @@ struct parse_events_error;
 
 struct option;
 struct perf_pmu;
+struct perf_pmus;
 struct strbuf;
 
 const char *event_type(size_t type);
@@ -156,6 +157,8 @@ struct parse_events_state {
 	struct parse_events_error *error;
 	/* Holds returned terms for term parsing. */
 	struct parse_events_terms *terms;
+	/* The PMUs to search during parsing. */
+	struct perf_pmus	  *pmus;
 	/* Start token. */
 	int			   stoken;
 	/* Use the fake PMU marker for testing. */
diff --git a/tools/perf/util/perf-regs-arch/perf_regs_x86.c b/tools/perf/util/perf-regs-arch/perf_regs_x86.c
index ea1d402b0893..2d80f119629a 100644
--- a/tools/perf/util/perf-regs-arch/perf_regs_x86.c
+++ b/tools/perf/util/perf-regs-arch/perf_regs_x86.c
@@ -9,6 +9,7 @@
 #include <linux/zalloc.h>
 
 #include "../debug.h"
+#include "../env.h"
 #include "../event.h"
 #include "../pmu.h"
 #include "../pmus.h"
@@ -248,6 +249,7 @@ static uint64_t __arch__reg_mask(u64 sample_type, u64 mask, bool has_simd_regs)
 		.exclude_kernel			= 1,
 		.sample_simd_regs_enabled	= has_simd_regs,
 	};
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	int fd;
 	/*
 	 * In an unnamed union, init it here to build on older gcc versions
@@ -258,7 +260,7 @@ static uint64_t __arch__reg_mask(u64 sample_type, u64 mask, bool has_simd_regs)
 	else
 		attr.sample_regs_user = mask;
 
-	if (perf_pmus__num_core_pmus() > 1) {
+	if (perf_pmus__num_core_pmus(pmus) > 1) {
 		struct perf_pmu *pmu = NULL;
 		__u64 type = PERF_TYPE_RAW;
 
@@ -512,6 +514,7 @@ __support_simd_reg_class(uint64_t sample_type, uint16_t qwords,
 		.exclude_kernel			= 1,
 		.sample_simd_regs_enabled	= 1,
 	};
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	int fd;
 
 	attr.sample_period = 1;
@@ -530,7 +533,7 @@ __support_simd_reg_class(uint64_t sample_type, uint16_t qwords,
 			attr.sample_simd_pred_reg_user = PERF_X86_SIMD_PRED_MASK;
 	}
 
-	if (perf_pmus__num_core_pmus() > 1) {
+	if (perf_pmus__num_core_pmus(pmus) > 1) {
 		__u64 type = perf_pmus__find_core_pmu()->type;
 
 		attr.config |= type << PERF_PMU_TYPE_SHIFT;
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 531641e66ef4..ee6a217dde91 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -1552,7 +1552,8 @@ static int pmu_config_term(const struct perf_pmu *pmu,
 			assert(term->val.num < PERF_COUNT_HW_MAX);
 			assert(pmu->is_core);
 			attr->config = term->val.num;
-			if (perf_pmus__supports_extended_type())
+			if (pmu->pmus ? perf_pmus__supports_extended_type(pmu->pmus)
+				      : perf_pmus__host_supports_extended_type())
 				attr->config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
 			attr->type = PERF_TYPE_HARDWARE;
 			break;
@@ -1570,7 +1571,8 @@ static int pmu_config_term(const struct perf_pmu *pmu,
 			assert((term->val.num & ~0xFFFFFF) == 0);
 			assert(pmu->is_core);
 			attr->config = term->val.num;
-			if (perf_pmus__supports_extended_type())
+			if (pmu->pmus ? perf_pmus__supports_extended_type(pmu->pmus)
+				      : perf_pmus__host_supports_extended_type())
 				attr->config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
 			attr->type = PERF_TYPE_HW_CACHE;
 			break;
@@ -2109,7 +2111,8 @@ bool perf_pmu__supports_legacy_cache(const struct perf_pmu *pmu)
 
 bool perf_pmu__auto_merge_stats(const struct perf_pmu *pmu)
 {
-	return !pmu->is_core || perf_pmus__num_core_pmus() == 1;
+	return !pmu->is_core ||
+	       perf_pmus__num_core_pmus(pmu->pmus ?: perf_env__pmus(host_env())) == 1;
 }
 
 bool perf_pmu__have_event(struct perf_pmu *pmu, const char *name)
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index ea55882ce670..7605c3859337 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -17,6 +17,7 @@ struct evsel_config_term;
 struct hashmap;
 struct perf_cpu_map;
 struct perf_env;
+struct perf_pmus;
 struct print_callbacks;
 
 enum {
@@ -163,6 +164,8 @@ struct perf_pmu {
 	bool sysfs_aliases_loaded;
 	/** @from_env: Was the PMU reconstructed from a perf_env header? */
 	bool from_env;
+	/** @pmus: The PMU collection this PMU belongs to. */
+	struct perf_pmus *pmus;
 	/**
 	 * @cpu_aliases_added: Have all json events table entries for the PMU
 	 * been added?
diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
index 4f4632f48e16..3adb297636b6 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -14,6 +14,7 @@
 #include "cpumap.h"
 #include "debug.h"
 #include "drm_pmu.h"
+#include "env.h"
 #include "evsel.h"
 #include "pmus.h"
 #include "pmu.h"
@@ -38,8 +39,6 @@
  *             are captured under other_pmus. PMUs belonging to other_pmus
  *             must have pmu->is_core=0 but pmu->is_uncore could be 0 or 1.
  */
-static LIST_HEAD(core_pmus);
-static LIST_HEAD(other_pmus);
 enum perf_tool_pmu_type {
 	PERF_TOOL_PMU_TYPE_PE_CORE,
 	PERF_TOOL_PMU_TYPE_PE_OTHER,
@@ -59,9 +58,8 @@ enum perf_tool_pmu_type {
 					PERF_TOOL_PMU_TYPE_HWMON_MASK | \
 					PERF_TOOL_PMU_TYPE_DRM_MASK)
 };
-static unsigned int read_pmu_types;
 
-static void pmu_read_sysfs(unsigned int to_read_pmus);
+static void pmu_read_sysfs(struct perf_pmus *pmus, unsigned int to_read_pmus);
 
 size_t pmu_name_len_no_suffix(const char *str)
 {
@@ -109,33 +107,50 @@ int pmu_name_cmp(const char *lhs_pmu_name, const char *rhs_pmu_name)
 	return lhs_num < rhs_num ? -1 : (lhs_num > rhs_num ? 1 : 0);
 }
 
-void perf_pmus__destroy(void)
+void perf_pmus__init(struct perf_pmus *pmus)
+{
+	INIT_LIST_HEAD(&pmus->core_pmus);
+	INIT_LIST_HEAD(&pmus->other_pmus);
+	pmus->read_pmu_types = 0;
+	pmus->num_core_pmus = 0;
+	pmus->extended_type = PERF_PMUS_EXTENDED_TYPE_UNKNOWN;
+	mutex_init(&pmus->lock);
+}
+
+void perf_pmus__exit(struct perf_pmus *pmus)
 {
 	struct perf_pmu *pmu, *tmp;
 
-	list_for_each_entry_safe(pmu, tmp, &core_pmus, list) {
+	if (!pmus->core_pmus.next || !pmus->other_pmus.next) {
+		perf_pmus__init(pmus);
+		return;
+	}
+
+	list_for_each_entry_safe(pmu, tmp, &pmus->core_pmus, list) {
 		list_del(&pmu->list);
 
 		perf_pmu__delete(pmu);
 	}
-	list_for_each_entry_safe(pmu, tmp, &other_pmus, list) {
+	list_for_each_entry_safe(pmu, tmp, &pmus->other_pmus, list) {
 		list_del(&pmu->list);
 
 		perf_pmu__delete(pmu);
 	}
-	read_pmu_types = 0;
+	pmus->read_pmu_types = 0;
+	pmus->num_core_pmus = 0;
+	pmus->extended_type = PERF_PMUS_EXTENDED_TYPE_UNKNOWN;
 }
 
-static struct perf_pmu *pmu_find(const char *name)
+static struct perf_pmu *pmu_find(struct perf_pmus *pmus, const char *name)
 {
 	struct perf_pmu *pmu;
 
-	list_for_each_entry(pmu, &core_pmus, list) {
+	list_for_each_entry(pmu, &pmus->core_pmus, list) {
 		if (!strcmp(pmu->name, name) ||
 		    (pmu->alias_name && !strcmp(pmu->alias_name, name)))
 			return pmu;
 	}
-	list_for_each_entry(pmu, &other_pmus, list) {
+	list_for_each_entry(pmu, &pmus->other_pmus, list) {
 		if (!strcmp(pmu->name, name) ||
 		    (pmu->alias_name && !strcmp(pmu->alias_name, name)))
 			return pmu;
@@ -144,7 +159,7 @@ static struct perf_pmu *pmu_find(const char *name)
 	return NULL;
 }
 
-struct perf_pmu *perf_pmus__find(const char *name)
+static struct perf_pmu *__perf_pmus__find(struct perf_pmus *pmus, const char *name)
 {
 	struct perf_pmu *pmu;
 	int dirfd;
@@ -158,24 +173,26 @@ struct perf_pmu *perf_pmus__find(const char *name)
 	 * so we keep us from multiple reading/parsing
 	 * the pmu format definitions.
 	 */
-	pmu = pmu_find(name);
+	pmu = pmu_find(pmus, name);
 	if (pmu)
 		return pmu;
 
-	if (read_pmu_types == PERF_TOOL_PMU_TYPE_ALL_MASK)
+	if (pmus->read_pmu_types == PERF_TOOL_PMU_TYPE_ALL_MASK)
 		return NULL;
 
 	core_pmu = is_pmu_core(name);
-	if (core_pmu && (read_pmu_types & PERF_TOOL_PMU_TYPE_PE_CORE_MASK))
+	if (core_pmu && (pmus->read_pmu_types & PERF_TOOL_PMU_TYPE_PE_CORE_MASK))
 		return NULL;
 
 	dirfd = perf_pmu__event_source_devices_fd();
-	pmu = perf_pmu__lookup(core_pmu ? &core_pmus : &other_pmus, dirfd, name,
+	pmu = perf_pmu__lookup(core_pmu ? &pmus->core_pmus : &pmus->other_pmus, dirfd, name,
 			       /*eager_load=*/false);
 	close(dirfd);
 
-	if (pmu)
+	if (pmu) {
+		pmu->pmus = pmus;
 		return pmu;
+	}
 
 	/* Looking up an individual perf event PMU failed, check if a tool PMU should be read. */
 	if (!strncmp(name, "hwmon_", 6))
@@ -186,8 +203,8 @@ struct perf_pmu *perf_pmus__find(const char *name)
 		to_read_pmus |= PERF_TOOL_PMU_TYPE_TOOL_MASK;
 
 	if (to_read_pmus) {
-		pmu_read_sysfs(to_read_pmus);
-		pmu = pmu_find(name);
+		pmu_read_sysfs(pmus, to_read_pmus);
+		pmu = pmu_find(pmus, name);
 		if (pmu)
 			return pmu;
 	}
@@ -195,11 +212,16 @@ struct perf_pmu *perf_pmus__find(const char *name)
 	to_read_pmus = PERF_TOOL_PMU_TYPE_PE_CORE_MASK;
 	if (!core_pmu)
 		to_read_pmus |= PERF_TOOL_PMU_TYPE_PE_OTHER_MASK;
-	pmu_read_sysfs(to_read_pmus);
-	return pmu_find(name);
+	pmu_read_sysfs(pmus, to_read_pmus);
+	return pmu_find(pmus, name);
+}
+
+struct perf_pmu *perf_pmus__find(const char *name)
+{
+	return __perf_pmus__find(perf_env__pmus(host_env()), name);
 }
 
-static struct perf_pmu *perf_pmu__find2(int dirfd, const char *name)
+static struct perf_pmu *perf_pmu__find2(struct perf_pmus *pmus, int dirfd, const char *name)
 {
 	struct perf_pmu *pmu;
 	bool core_pmu;
@@ -209,19 +231,22 @@ static struct perf_pmu *perf_pmu__find2(int dirfd, const char *name)
 	 * so we keep us from multiple reading/parsing
 	 * the pmu format definitions.
 	 */
-	pmu = pmu_find(name);
+	pmu = pmu_find(pmus, name);
 	if (pmu)
 		return pmu;
 
-	if (read_pmu_types == PERF_TOOL_PMU_TYPE_ALL_MASK)
+	if (pmus->read_pmu_types == PERF_TOOL_PMU_TYPE_ALL_MASK)
 		return NULL;
 
 	core_pmu = is_pmu_core(name);
-	if (core_pmu && (read_pmu_types & PERF_TOOL_PMU_TYPE_PE_CORE_MASK))
+	if (core_pmu && (pmus->read_pmu_types & PERF_TOOL_PMU_TYPE_PE_CORE_MASK))
 		return NULL;
 
-	return perf_pmu__lookup(core_pmu ? &core_pmus : &other_pmus, dirfd, name,
-				/*eager_load=*/false);
+	pmu = perf_pmu__lookup(core_pmu ? &pmus->core_pmus : &pmus->other_pmus, dirfd, name,
+			       /*eager_load=*/false);
+	if (pmu)
+		pmu->pmus = pmus;
+	return pmu;
 }
 
 static int pmus_cmp(void *priv __maybe_unused,
@@ -234,11 +259,11 @@ static int pmus_cmp(void *priv __maybe_unused,
 }
 
 /* Add all pmus in sysfs to pmu list: */
-static void pmu_read_sysfs(unsigned int to_read_types)
+static void pmu_read_sysfs(struct perf_pmus *pmus, unsigned int to_read_types)
 {
-	struct perf_pmu *tool_pmu;
+	struct perf_pmu *pmu, *tool_pmu;
 
-	if ((read_pmu_types & to_read_types) == to_read_types) {
+	if ((pmus->read_pmu_types & to_read_types) == to_read_types) {
 		/* All requested PMU types have been read. */
 		return;
 	}
@@ -259,60 +284,68 @@ static void pmu_read_sysfs(unsigned int to_read_types)
 				continue;
 			if (core_only && !is_pmu_core(dent->d_name))
 				continue;
-			/* add to static LIST_HEAD(core_pmus) or LIST_HEAD(other_pmus): */
-			perf_pmu__find2(fd, dent->d_name);
+			/* add to core_pmus or other_pmus: */
+			perf_pmu__find2(pmus, fd, dent->d_name);
 		}
 
 		close(fd);
 	}
 skip_pe_pmus:
-	if ((to_read_types & PERF_TOOL_PMU_TYPE_PE_CORE_MASK) && list_empty(&core_pmus)) {
-		if (!perf_pmu__create_placeholder_core_pmu(&core_pmus))
+	if ((to_read_types & PERF_TOOL_PMU_TYPE_PE_CORE_MASK) && list_empty(&pmus->core_pmus)) {
+		pmu = perf_pmu__create_placeholder_core_pmu(&pmus->core_pmus);
+		if (!pmu)
 			pr_err("Failure to set up any core PMUs\n");
+		else
+			pmu->pmus = pmus;
 	}
-	list_sort(NULL, &core_pmus, pmus_cmp);
+	list_sort(NULL, &pmus->core_pmus, pmus_cmp);
 
 	if ((to_read_types & PERF_TOOL_PMU_TYPE_TOOL_MASK) != 0 &&
-	    (read_pmu_types & PERF_TOOL_PMU_TYPE_TOOL_MASK) == 0) {
+	    (pmus->read_pmu_types & PERF_TOOL_PMU_TYPE_TOOL_MASK) == 0) {
 		tool_pmu = tool_pmu__new();
-		if (tool_pmu)
-			list_add_tail(&tool_pmu->list, &other_pmus);
+		if (tool_pmu) {
+			tool_pmu->pmus = pmus;
+			list_add_tail(&tool_pmu->list, &pmus->other_pmus);
+		}
 	}
 	if ((to_read_types & PERF_TOOL_PMU_TYPE_HWMON_MASK) != 0 &&
-	    (read_pmu_types & PERF_TOOL_PMU_TYPE_HWMON_MASK) == 0)
-		perf_pmus__read_hwmon_pmus(&other_pmus);
+	    (pmus->read_pmu_types & PERF_TOOL_PMU_TYPE_HWMON_MASK) == 0)
+		perf_pmus__read_hwmon_pmus(&pmus->other_pmus);
 
 	if ((to_read_types & PERF_TOOL_PMU_TYPE_DRM_MASK) != 0 &&
-	    (read_pmu_types & PERF_TOOL_PMU_TYPE_DRM_MASK) == 0)
-		perf_pmus__read_drm_pmus(&other_pmus);
+	    (pmus->read_pmu_types & PERF_TOOL_PMU_TYPE_DRM_MASK) == 0)
+		perf_pmus__read_drm_pmus(&pmus->other_pmus);
+
+	list_for_each_entry(pmu, &pmus->other_pmus, list)
+		pmu->pmus = pmus;
 
-	list_sort(NULL, &other_pmus, pmus_cmp);
+	list_sort(NULL, &pmus->other_pmus, pmus_cmp);
 
-	read_pmu_types |= to_read_types;
+	pmus->read_pmu_types |= to_read_types;
 }
 
-static struct perf_pmu *__perf_pmus__find_by_type(unsigned int type)
+static struct perf_pmu *__perf_pmus__find_by_type(struct perf_pmus *pmus, unsigned int type)
 {
 	struct perf_pmu *pmu;
 
-	list_for_each_entry(pmu, &core_pmus, list) {
+	list_for_each_entry(pmu, &pmus->core_pmus, list) {
 		if (pmu->type == type)
 			return pmu;
 	}
 
-	list_for_each_entry(pmu, &other_pmus, list) {
+	list_for_each_entry(pmu, &pmus->other_pmus, list) {
 		if (pmu->type == type)
 			return pmu;
 	}
 	return NULL;
 }
 
-struct perf_pmu *perf_pmus__find_by_type(unsigned int type)
+static struct perf_pmu *pmu_find_by_type(struct perf_pmus *pmus, unsigned int type)
 {
 	unsigned int to_read_pmus;
-	struct perf_pmu *pmu = __perf_pmus__find_by_type(type);
+	struct perf_pmu *pmu = __perf_pmus__find_by_type(pmus, type);
 
-	if (pmu || (read_pmu_types == PERF_TOOL_PMU_TYPE_ALL_MASK))
+	if (pmu || (pmus->read_pmu_types == PERF_TOOL_PMU_TYPE_ALL_MASK))
 		return pmu;
 
 	if (type >= PERF_PMU_TYPE_PE_START && type <= PERF_PMU_TYPE_PE_END) {
@@ -325,48 +358,64 @@ struct perf_pmu *perf_pmus__find_by_type(unsigned int type)
 	} else {
 		to_read_pmus = PERF_TOOL_PMU_TYPE_TOOL_MASK;
 	}
-	pmu_read_sysfs(to_read_pmus);
-	pmu = __perf_pmus__find_by_type(type);
+	pmu_read_sysfs(pmus, to_read_pmus);
+	pmu = __perf_pmus__find_by_type(pmus, type);
 	return pmu;
 }
 
+struct perf_pmu *perf_pmus__find_by_type(unsigned int type)
+{
+	return pmu_find_by_type(perf_env__pmus(host_env()), type);
+}
+
 /*
  * pmu iterator: If pmu is NULL, we start at the begin, otherwise return the
  * next pmu. Returns NULL on end.
  */
-struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu)
+static struct perf_pmu *pmu_scan(struct perf_pmus *pmus, struct perf_pmu *pmu)
 {
 	bool use_core_pmus = !pmu || pmu->is_core;
 
 	if (!pmu) {
-		pmu_read_sysfs(PERF_TOOL_PMU_TYPE_ALL_MASK);
-		pmu = list_prepare_entry(pmu, &core_pmus, list);
+		pmu_read_sysfs(pmus, PERF_TOOL_PMU_TYPE_ALL_MASK);
+		pmu = list_prepare_entry(pmu, &pmus->core_pmus, list);
 	}
 	if (use_core_pmus) {
-		list_for_each_entry_continue(pmu, &core_pmus, list)
+		list_for_each_entry_continue(pmu, &pmus->core_pmus, list)
 			return pmu;
 
 		pmu = NULL;
-		pmu = list_prepare_entry(pmu, &other_pmus, list);
+		pmu = list_prepare_entry(pmu, &pmus->other_pmus, list);
 	}
-	list_for_each_entry_continue(pmu, &other_pmus, list)
+	list_for_each_entry_continue(pmu, &pmus->other_pmus, list)
 		return pmu;
 	return NULL;
 }
 
-struct perf_pmu *perf_pmus__scan_core(struct perf_pmu *pmu)
+struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu)
+{
+	return pmu_scan(perf_env__pmus(host_env()), pmu);
+}
+
+static struct perf_pmu *pmu_scan_core(struct perf_pmus *pmus, struct perf_pmu *pmu)
 {
 	if (!pmu) {
-		pmu_read_sysfs(PERF_TOOL_PMU_TYPE_PE_CORE_MASK);
-		return list_first_entry_or_null(&core_pmus, typeof(*pmu), list);
+		pmu_read_sysfs(pmus, PERF_TOOL_PMU_TYPE_PE_CORE_MASK);
+		return list_first_entry_or_null(&pmus->core_pmus, typeof(*pmu), list);
 	}
-	list_for_each_entry_continue(pmu, &core_pmus, list)
+	list_for_each_entry_continue(pmu, &pmus->core_pmus, list)
 		return pmu;
 
 	return NULL;
 }
 
-struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmu *pmu, const char *event)
+struct perf_pmu *perf_pmus__scan_core(struct perf_pmu *pmu)
+{
+	return pmu_scan_core(perf_env__pmus(host_env()), pmu);
+}
+
+struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmus *pmus,
+					   struct perf_pmu *pmu, const char *event)
 {
 	bool use_core_pmus = !pmu || pmu->is_core;
 
@@ -390,22 +439,23 @@ struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmu *pmu, const char *eve
 		if (strlen(event) > 4 && strncmp("drm-", event, 4) == 0)
 			to_read_pmus |= PERF_TOOL_PMU_TYPE_DRM_MASK;
 
-		pmu_read_sysfs(to_read_pmus);
-		pmu = list_prepare_entry(pmu, &core_pmus, list);
+		pmu_read_sysfs(pmus, to_read_pmus);
+		pmu = list_prepare_entry(pmu, &pmus->core_pmus, list);
 	}
 	if (use_core_pmus) {
-		list_for_each_entry_continue(pmu, &core_pmus, list)
+		list_for_each_entry_continue(pmu, &pmus->core_pmus, list)
 			return pmu;
 
 		pmu = NULL;
-		pmu = list_prepare_entry(pmu, &other_pmus, list);
+		pmu = list_prepare_entry(pmu, &pmus->other_pmus, list);
 	}
-	list_for_each_entry_continue(pmu, &other_pmus, list)
+	list_for_each_entry_continue(pmu, &pmus->other_pmus, list)
 		return pmu;
 	return NULL;
 }
 
-struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const char *wildcard)
+struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmus *pmus,
+						   struct perf_pmu *pmu, const char *wildcard)
 {
 	bool use_core_pmus = !pmu || pmu->is_core;
 
@@ -434,33 +484,33 @@ struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const c
 			to_read_pmus |= PERF_TOOL_PMU_TYPE_HWMON_MASK;
 		}
 
-		pmu_read_sysfs(to_read_pmus);
-		pmu = list_prepare_entry(pmu, &core_pmus, list);
+		pmu_read_sysfs(pmus, to_read_pmus);
+		pmu = list_prepare_entry(pmu, &pmus->core_pmus, list);
 	}
 	if (use_core_pmus) {
-		list_for_each_entry_continue(pmu, &core_pmus, list) {
+		list_for_each_entry_continue(pmu, &pmus->core_pmus, list) {
 			if (perf_pmu__wildcard_match(pmu, wildcard))
 				return pmu;
 		}
 		pmu = NULL;
-		pmu = list_prepare_entry(pmu, &other_pmus, list);
+		pmu = list_prepare_entry(pmu, &pmus->other_pmus, list);
 	}
-	list_for_each_entry_continue(pmu, &other_pmus, list) {
+	list_for_each_entry_continue(pmu, &pmus->other_pmus, list) {
 		if (perf_pmu__wildcard_match(pmu, wildcard))
 			return pmu;
 	}
 	return NULL;
 }
 
-static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu)
+static struct perf_pmu *pmu_scan_skip_duplicates(struct perf_pmus *pmus, struct perf_pmu *pmu)
 {
 	bool use_core_pmus = !pmu || pmu->is_core;
 	int last_pmu_name_len = 0;
 	const char *last_pmu_name = (pmu && pmu->name) ? pmu->name : "";
 
 	if (!pmu) {
-		pmu_read_sysfs(PERF_TOOL_PMU_TYPE_ALL_MASK);
-		pmu = list_prepare_entry(pmu, &core_pmus, list);
+		pmu_read_sysfs(pmus, PERF_TOOL_PMU_TYPE_ALL_MASK);
+		pmu = list_prepare_entry(pmu, &pmus->core_pmus, list);
 	} else
 		last_pmu_name_len = pmu_name_len_no_suffix(pmu->name ?: "");
 
@@ -470,13 +520,13 @@ static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu)
 		 * may differ only by a suffix but have different events, so
 		 * they are never duplicates.
 		 */
-		list_for_each_entry_continue(pmu, &core_pmus, list)
+		list_for_each_entry_continue(pmu, &pmus->core_pmus, list)
 			return pmu;
 
 		pmu = NULL;
-		pmu = list_prepare_entry(pmu, &other_pmus, list);
+		pmu = list_prepare_entry(pmu, &pmus->other_pmus, list);
 	}
-	list_for_each_entry_continue(pmu, &other_pmus, list) {
+	list_for_each_entry_continue(pmu, &pmus->other_pmus, list) {
 		int pmu_name_len = pmu_name_len_no_suffix(pmu->name ?: "");
 
 		if (last_pmu_name_len == pmu_name_len &&
@@ -488,27 +538,28 @@ static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu)
 	return NULL;
 }
 
-struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmu *pmu, const char *compat)
+struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmus *pmus,
+					       struct perf_pmu *pmu, const char *compat)
 {
 	if (!pmu) {
 		/* Only uncore PMUs can have identifiers. */
 		unsigned int to_read_pmus = PERF_TOOL_PMU_TYPE_PE_OTHER_MASK;
 
-		pmu_read_sysfs(to_read_pmus);
-		pmu = list_prepare_entry(pmu, &other_pmus, list);
+		pmu_read_sysfs(pmus, to_read_pmus);
+		pmu = list_prepare_entry(pmu, &pmus->other_pmus, list);
 	}
-	list_for_each_entry_continue(pmu, &other_pmus, list) {
+	list_for_each_entry_continue(pmu, &pmus->other_pmus, list) {
 		if (pmu->id && pmu_uncore_identifier_match(compat, pmu->id))
 			return pmu;
 	}
 	return NULL;
 }
 
-const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(const char *str)
+const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(struct perf_pmus *pmus, const char *str)
 {
 	struct perf_pmu *pmu = NULL;
 
-	while ((pmu = perf_pmus__scan(pmu)) != NULL) {
+	while ((pmu = pmu_scan(pmus, pmu)) != NULL) {
 		if (!strcmp(pmu->name, str))
 			return pmu;
 		/* Ignore "uncore_" prefix. */
@@ -629,7 +680,8 @@ static int perf_pmus__print_pmu_events__callback(void *vstate,
 	return 0;
 }
 
-void perf_pmus__print_pmu_events(const struct print_callbacks *print_cb, void *print_state)
+void perf_pmus__print_pmu_events(struct perf_pmus *pmus,
+				 const struct print_callbacks *print_cb, void *print_state)
 {
 	struct perf_pmu *pmu;
 	int printed = 0;
@@ -637,16 +689,16 @@ void perf_pmus__print_pmu_events(const struct print_callbacks *print_cb, void *p
 	struct sevent *aliases;
 	struct events_callback_state state;
 	bool skip_duplicate_pmus = print_cb->skip_duplicate_pmus(print_state);
-	struct perf_pmu *(*scan_fn)(struct perf_pmu *);
+	struct perf_pmu *(*scan_fn)(struct perf_pmus *p, struct perf_pmu *cur);
 
 	if (skip_duplicate_pmus)
-		scan_fn = perf_pmus__scan_skip_duplicates;
+		scan_fn = pmu_scan_skip_duplicates;
 	else
-		scan_fn = perf_pmus__scan;
+		scan_fn = pmu_scan;
 
 	pmu = NULL;
 	len = 0;
-	while ((pmu = scan_fn(pmu)) != NULL)
+	while ((pmu = scan_fn(pmus, pmu)) != NULL)
 		len += perf_pmu__num_events(pmu);
 
 	aliases = calloc(len, sizeof(struct sevent));
@@ -660,7 +712,7 @@ void perf_pmus__print_pmu_events(const struct print_callbacks *print_cb, void *p
 		.aliases_len = len,
 		.index = 0,
 	};
-	while ((pmu = scan_fn(pmu)) != NULL) {
+	while ((pmu = scan_fn(pmus, pmu)) != NULL) {
 		perf_pmu__for_each_event(pmu, skip_duplicate_pmus, &state,
 					 perf_pmus__print_pmu_events__callback);
 	}
@@ -744,18 +796,19 @@ static int build_format_string(void *state, const char *name, int config,
 	return ret1 < 0 ? ret1 : (ret2 < 0 ? ret2 : 0);
 }
 
-void perf_pmus__print_raw_pmu_events(const struct print_callbacks *print_cb, void *print_state)
+void perf_pmus__print_raw_pmu_events(struct perf_pmus *pmus,
+				     const struct print_callbacks *print_cb, void *print_state)
 {
 	bool skip_duplicate_pmus = print_cb->skip_duplicate_pmus(print_state);
-	struct perf_pmu *(*scan_fn)(struct perf_pmu *);
+	struct perf_pmu *(*scan_fn)(struct perf_pmus *p, struct perf_pmu *cur);
 	struct perf_pmu *pmu = NULL;
 
 	if (skip_duplicate_pmus)
-		scan_fn = perf_pmus__scan_skip_duplicates;
+		scan_fn = pmu_scan_skip_duplicates;
 	else
-		scan_fn = perf_pmus__scan;
+		scan_fn = pmu_scan;
 
-	while ((pmu = scan_fn(pmu)) != NULL) {
+	while ((pmu = scan_fn(pmus, pmu)) != NULL) {
 		struct build_format_string_args format_args = {
 			.short_string = STRBUF_INIT,
 			.long_string = STRBUF_INIT,
@@ -797,67 +850,79 @@ void perf_pmus__print_raw_pmu_events(const struct print_callbacks *print_cb, voi
 	}
 }
 
-bool perf_pmus__have_event(const char *pname, const char *name)
+bool perf_pmus__have_event(struct perf_pmus *pmus, const char *pname, const char *name)
 {
-	struct perf_pmu *pmu = perf_pmus__find(pname);
+	struct perf_pmu *pmu = __perf_pmus__find(pmus, pname);
 
 	return pmu && perf_pmu__have_event(pmu, name);
 }
 
-int perf_pmus__num_core_pmus(void)
+int perf_pmus__num_core_pmus(struct perf_pmus *pmus)
 {
-	static int count;
-
-	if (!count) {
+	if (!pmus->num_core_pmus) {
 		struct perf_pmu *pmu = NULL;
 
-		while ((pmu = perf_pmus__scan_core(pmu)) != NULL)
-			count++;
+		while ((pmu = pmu_scan_core(pmus, pmu)) != NULL)
+			pmus->num_core_pmus++;
 	}
-	return count;
+	return pmus->num_core_pmus;
 }
 
-static bool __perf_pmus__supports_extended_type(void)
+static bool __perf_pmus__supports_extended_type(struct perf_pmus *pmus)
 {
 	struct perf_pmu *pmu = NULL;
 
-	if (perf_pmus__num_core_pmus() <= 1)
+	if (perf_pmus__num_core_pmus(pmus) <= 1)
 		return false;
 
-	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
-		if (!is_event_supported(PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES | ((__u64)pmu->type << PERF_PMU_TYPE_SHIFT)))
+	while ((pmu = pmu_scan_core(pmus, pmu)) != NULL) {
+		if (!pmu->from_env &&
+		    !is_event_supported(PERF_TYPE_HARDWARE,
+					PERF_COUNT_HW_CPU_CYCLES |
+					((__u64)pmu->type << PERF_PMU_TYPE_SHIFT)))
 			return false;
 	}
 
 	return true;
 }
 
-static bool perf_pmus__do_support_extended_type;
-
-static void perf_pmus__init_supports_extended_type(void)
+bool perf_pmus__supports_extended_type(struct perf_pmus *pmus)
 {
-	perf_pmus__do_support_extended_type = __perf_pmus__supports_extended_type();
-}
+	enum perf_pmus_extended_type extended_type =
+		__atomic_load_n(&pmus->extended_type, __ATOMIC_ACQUIRE);
 
-bool perf_pmus__supports_extended_type(void)
-{
-	static pthread_once_t extended_type_once = PTHREAD_ONCE_INIT;
+	if (extended_type == PERF_PMUS_EXTENDED_TYPE_UNKNOWN) {
+		mutex_lock(&pmus->lock);
+		extended_type = pmus->extended_type;
+		if (extended_type == PERF_PMUS_EXTENDED_TYPE_UNKNOWN) {
+			extended_type = __perf_pmus__supports_extended_type(pmus)
+				? PERF_PMUS_EXTENDED_TYPE_SUPPORTED
+				: PERF_PMUS_EXTENDED_TYPE_NOT_SUPPORTED;
+			__atomic_store_n(&pmus->extended_type, extended_type,
+					 __ATOMIC_RELEASE);
+		}
+		mutex_unlock(&pmus->lock);
+	}
 
-	pthread_once(&extended_type_once, perf_pmus__init_supports_extended_type);
+	return extended_type == PERF_PMUS_EXTENDED_TYPE_SUPPORTED;
+}
 
-	return perf_pmus__do_support_extended_type;
+bool perf_pmus__host_supports_extended_type(void)
+{
+	return perf_pmus__supports_extended_type(perf_env__pmus(host_env()));
 }
 
-struct perf_pmu *perf_pmus__find_by_attr(const struct perf_event_attr *attr)
+struct perf_pmu *perf_pmus__find_by_attr(struct perf_pmus *pmus,
+					 const struct perf_event_attr *attr)
 {
-	struct perf_pmu *pmu = perf_pmus__find_by_type(attr->type);
+	struct perf_pmu *pmu = pmu_find_by_type(pmus, attr->type);
 	u32 type = attr->type;
 	bool legacy_core_type = type == PERF_TYPE_HARDWARE || type == PERF_TYPE_HW_CACHE;
 
-	if (!pmu && legacy_core_type && perf_pmus__supports_extended_type()) {
+	if (!pmu && legacy_core_type && perf_pmus__supports_extended_type(pmus)) {
 		type = attr->config >> PERF_PMU_TYPE_SHIFT;
 
-		pmu = perf_pmus__find_by_type(type);
+		pmu = pmu_find_by_type(pmus, type);
 	}
 	if (!pmu && (legacy_core_type || type == PERF_TYPE_RAW)) {
 		/*
@@ -868,7 +933,7 @@ struct perf_pmu *perf_pmus__find_by_attr(const struct perf_event_attr *attr)
 		 * PERF_TYPE_RAW, assume the RAW events are going to be handled
 		 * by the first core PMU.
 		 */
-		pmu = perf_pmus__find_core_pmu();
+		pmu = pmu_scan_core(pmus, NULL);
 	}
 	return pmu;
 }
@@ -876,11 +941,13 @@ struct perf_pmu *perf_pmus__find_by_attr(const struct perf_event_attr *attr)
 struct perf_pmu *evsel__find_pmu(const struct evsel *evsel)
 {
 	struct perf_pmu *pmu = evsel->pmu;
+	struct perf_env *env;
 
 	if (pmu)
 		return pmu;
 
-	pmu = perf_pmus__find_by_attr(&evsel->core.attr);
+	env = evsel__env((struct evsel *)evsel) ?: host_env();
+	pmu = perf_pmus__find_by_attr(perf_env__pmus(env), &evsel->core.attr);
 	((struct evsel *)evsel)->pmu = pmu;
 	return pmu;
 }
@@ -890,21 +957,37 @@ struct perf_pmu *perf_pmus__find_core_pmu(void)
 	return perf_pmus__scan_core(NULL);
 }
 
-struct perf_pmu *perf_pmus__add_test_pmu(int test_sysfs_dirfd, const char *name)
+struct perf_pmu *perf_pmus__add_test_pmu(struct perf_pmus *pmus,
+					 int test_sysfs_dirfd, const char *name)
 {
+	struct perf_pmu *pmu;
+
+	if (!pmus->core_pmus.next || !pmus->other_pmus.next)
+		perf_pmus__init(pmus);
 	/*
 	 * Some PMU functions read from the sysfs mount point, so care is
 	 * needed, hence passing the eager_load flag to load things like the
 	 * format files.
 	 */
-	return perf_pmu__lookup(&other_pmus, test_sysfs_dirfd, name, /*eager_load=*/true);
+	pmu = perf_pmu__lookup(&pmus->other_pmus, test_sysfs_dirfd, name, /*eager_load=*/true);
+	if (pmu)
+		pmu->pmus = pmus;
+	return pmu;
 }
 
-struct perf_pmu *perf_pmus__add_test_hwmon_pmu(const char *hwmon_dir,
+struct perf_pmu *perf_pmus__add_test_hwmon_pmu(struct perf_pmus *pmus,
+					       const char *hwmon_dir,
 					       const char *sysfs_name,
 					       const char *name)
 {
-	return hwmon_pmu__new(&other_pmus, hwmon_dir, sysfs_name, name);
+	struct perf_pmu *pmu;
+
+	if (!pmus->core_pmus.next || !pmus->other_pmus.next)
+		perf_pmus__init(pmus);
+	pmu = hwmon_pmu__new(&pmus->other_pmus, hwmon_dir, sysfs_name, name);
+	if (pmu)
+		pmu->pmus = pmus;
+	return pmu;
 }
 
 struct perf_pmu *perf_pmus__fake_pmu(void)
diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h
index 0d55edb3f2fc..45f4bd8b6aa6 100644
--- a/tools/perf/util/pmus.h
+++ b/tools/perf/util/pmus.h
@@ -2,41 +2,220 @@
 #ifndef __PMUS_H
 #define __PMUS_H
 
+#include <linux/list.h>
 #include <stdbool.h>
 #include <stddef.h>
+#include "mutex.h"
 
 struct perf_event_attr;
 struct perf_pmu;
 struct print_callbacks;
 
+/**
+ * enum perf_pmus_extended_type - Tri-state cache for extended PMU type support.
+ * @PERF_PMUS_EXTENDED_TYPE_UNKNOWN: Support has not been checked yet.
+ * @PERF_PMUS_EXTENDED_TYPE_SUPPORTED: Multiple core PMUs / extended type supported.
+ * @PERF_PMUS_EXTENDED_TYPE_NOT_SUPPORTED: Extended type not supported.
+ */
+enum perf_pmus_extended_type {
+	PERF_PMUS_EXTENDED_TYPE_UNKNOWN = 0,
+	PERF_PMUS_EXTENDED_TYPE_SUPPORTED,
+	PERF_PMUS_EXTENDED_TYPE_NOT_SUPPORTED,
+};
+
+/**
+ * struct perf_pmus - Collection of core and non-core PMUs for an environment.
+ */
+struct perf_pmus {
+	/**
+	 * @core_pmus: In-order list of core PMUs (e.g. "cpu", "cpu_core",
+	 * "cpu_atom").
+	 */
+	struct list_head core_pmus;
+	/**
+	 * @other_pmus: In-order list of non-core PMUs (uncore, software,
+	 * tracepoint, tool, hwmon, drm, etc.).
+	 */
+	struct list_head other_pmus;
+	/**
+	 * @read_pmu_types: Bitmask of enum perf_tool_pmu_type values whose PMU
+	 * lists have been populated.
+	 */
+	unsigned int read_pmu_types;
+	/** @num_core_pmus: Cached count of entries in @core_pmus. */
+	int num_core_pmus;
+	/**
+	 * @extended_type: Cached result of checking whether extended PMU type
+	 * encoding in perf_event_attr::config is supported.
+	 */
+	enum perf_pmus_extended_type extended_type;
+	/** @lock: Protects lazy initialization of @extended_type. */
+	struct mutex lock;
+};
+
+#define PERF_PMUS_INIT(name) {					\
+	.core_pmus = LIST_HEAD_INIT((name).core_pmus),		\
+	.other_pmus = LIST_HEAD_INIT((name).other_pmus),	\
+	.lock = { .lock = __PERF_MUTEX_INITIALIZER },		\
+}
+
 size_t pmu_name_len_no_suffix(const char *str);
 /* Exposed for testing only. */
 int pmu_name_cmp(const char *lhs_pmu_name, const char *rhs_pmu_name);
 
-void perf_pmus__destroy(void);
+/**
+ * perf_pmus__init() - Initialize an empty struct perf_pmus.
+ * @pmus: The PMU collection to initialize.
+ */
+void perf_pmus__init(struct perf_pmus *pmus);
+
+/**
+ * perf_pmus__exit() - Free all PMUs in @pmus and reset its state.
+ * @pmus: The PMU collection to clean up.
+ */
+void perf_pmus__exit(struct perf_pmus *pmus);
 
 struct perf_pmu *perf_pmus__find(const char *name);
 struct perf_pmu *perf_pmus__find_by_type(unsigned int type);
-struct perf_pmu *perf_pmus__find_by_attr(const struct perf_event_attr *attr);
+
+/**
+ * perf_pmus__find_by_attr() - Find the PMU corresponding to @attr.
+ * @pmus: The PMU collection to search.
+ * @attr: The perf_event_attr whose type/config identifies the PMU.
+ *
+ * Return: Pointer to the matching struct perf_pmu, or NULL if not found.
+ */
+struct perf_pmu *perf_pmus__find_by_attr(struct perf_pmus *pmus,
+					 const struct perf_event_attr *attr);
 
 struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu);
 struct perf_pmu *perf_pmus__scan_core(struct perf_pmu *pmu);
-struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmu *pmu, const char *event);
-struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const char *wildcard);
-struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmu *pmu, const char *compat);
 
-const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(const char *str);
+/**
+ * perf_pmus__scan_for_event() - Iterate over PMUs in @pmus that have @event.
+ * @pmus: The PMU collection to scan.
+ * @pmu: Previous matching PMU returned, or NULL to start.
+ * @event: Event name to search for.
+ *
+ * Return: Next struct perf_pmu supporting @event, or NULL when done.
+ */
+struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmus *pmus,
+					   struct perf_pmu *pmu, const char *event);
+
+/**
+ * perf_pmus__scan_matching_wildcard() - Iterate over PMUs matching @wildcard.
+ * @pmus: The PMU collection to scan.
+ * @pmu: Previous matching PMU returned, or NULL to start.
+ * @wildcard: Wildcard/prefix pattern to match against PMU names.
+ *
+ * Return: Next matching struct perf_pmu, or NULL when done.
+ */
+struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmus *pmus,
+						   struct perf_pmu *pmu, const char *wildcard);
+
+/**
+ * perf_pmus__scan_for_uncore_id() - Iterate over uncore PMUs matching @compat.
+ * @pmus: The PMU collection to scan.
+ * @pmu: Previous matching PMU returned, or NULL to start.
+ * @compat: Uncore identifier string to match.
+ *
+ * Return: Next matching uncore struct perf_pmu, or NULL when done.
+ */
+struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmus *pmus,
+					       struct perf_pmu *pmu, const char *compat);
+
+/**
+ * perf_pmus__pmu_for_pmu_filter() - Find PMU matching a pmu_filter string.
+ * @pmus: The PMU collection to search.
+ * @str: Filter string to match against PMU names.
+ *
+ * Return: Matching struct perf_pmu, or NULL if none matches.
+ */
+const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(struct perf_pmus *pmus, const char *str);
 
-void perf_pmus__print_pmu_events(const struct print_callbacks *print_cb, void *print_state);
-void perf_pmus__print_raw_pmu_events(const struct print_callbacks *print_cb, void *print_state);
-bool perf_pmus__have_event(const char *pname, const char *name);
-int perf_pmus__num_core_pmus(void);
-bool perf_pmus__supports_extended_type(void);
+/**
+ * perf_pmus__print_pmu_events() - Print formatted PMU events via @print_cb.
+ * @pmus: The PMU collection whose events to print.
+ * @print_cb: Callbacks for printing event entries.
+ * @print_state: Caller state passed to @print_cb.
+ */
+void perf_pmus__print_pmu_events(struct perf_pmus *pmus,
+				 const struct print_callbacks *print_cb, void *print_state);
 
-struct perf_pmu *perf_pmus__add_test_pmu(int test_sysfs_dirfd, const char *name);
-struct perf_pmu *perf_pmus__add_test_hwmon_pmu(const char *hwmon_dir,
+/**
+ * perf_pmus__print_raw_pmu_events() - Print raw PMU event descriptors.
+ * @pmus: The PMU collection whose raw events to print.
+ * @print_cb: Callbacks for printing event entries.
+ * @print_state: Caller state passed to @print_cb.
+ */
+void perf_pmus__print_raw_pmu_events(struct perf_pmus *pmus,
+				     const struct print_callbacks *print_cb, void *print_state);
+
+/**
+ * perf_pmus__have_event() - Check whether @pname in @pmus has event @name.
+ * @pmus: The PMU collection to search.
+ * @pname: PMU name.
+ * @name: Event name.
+ *
+ * Return: true if the PMU exists and has @name, false otherwise.
+ */
+bool perf_pmus__have_event(struct perf_pmus *pmus, const char *pname, const char *name);
+
+/**
+ * perf_pmus__num_core_pmus() - Return the number of core PMUs in @pmus.
+ * @pmus: The PMU collection to query.
+ *
+ * Return: Number of core PMUs.
+ */
+int perf_pmus__num_core_pmus(struct perf_pmus *pmus);
+
+/**
+ * perf_pmus__supports_extended_type() - Check whether extended PMU type
+ *                                       encoding is supported in @pmus.
+ * @pmus: The PMU collection to query.
+ *
+ * Return: true if extended type encoding is supported, false otherwise.
+ */
+bool perf_pmus__supports_extended_type(struct perf_pmus *pmus);
+
+/**
+ * perf_pmus__host_supports_extended_type() - Check whether extended PMU type
+ *                                            encoding is supported on the host.
+ *
+ * Return: true if extended type encoding is supported on the live host.
+ */
+bool perf_pmus__host_supports_extended_type(void);
+
+/**
+ * perf_pmus__add_test_pmu() - Add a sysfs-backed test PMU to @pmus.
+ * @pmus: The PMU collection to add the test PMU to.
+ * @test_sysfs_dirfd: Directory fd of the synthetic sysfs root.
+ * @name: Name of the test PMU.
+ *
+ * Return: Pointer to the created struct perf_pmu, or NULL on failure.
+ */
+struct perf_pmu *perf_pmus__add_test_pmu(struct perf_pmus *pmus,
+					 int test_sysfs_dirfd, const char *name);
+
+/**
+ * perf_pmus__add_test_hwmon_pmu() - Add a synthetic hwmon test PMU to @pmus.
+ * @pmus: The PMU collection to add the test hwmon PMU to.
+ * @hwmon_dir: Path to the synthetic hwmon directory.
+ * @sysfs_name: Directory entry name (e.g. "hwmon0").
+ * @name: Logical name of the hwmon device.
+ *
+ * Return: Pointer to the created struct perf_pmu, or NULL on failure.
+ */
+struct perf_pmu *perf_pmus__add_test_hwmon_pmu(struct perf_pmus *pmus,
+					       const char *hwmon_dir,
 					       const char *sysfs_name,
 					       const char *name);
+
+/**
+ * perf_pmus__fake_pmu() - Return the singleton fake PMU used for metric tests.
+ *
+ * Return: Pointer to the fake struct perf_pmu.
+ */
 struct perf_pmu *perf_pmus__fake_pmu(void);
 struct perf_pmu *perf_pmus__find_core_pmu(void);
 
diff --git a/tools/perf/util/print-events.c b/tools/perf/util/print-events.c
index 77428d9e73c4..fe48efe9aefb 100644
--- a/tools/perf/util/print-events.c
+++ b/tools/perf/util/print-events.c
@@ -17,6 +17,7 @@
 
 #include "build-id.h"
 #include "debug.h"
+#include "env.h"
 #include "evsel.h"
 #include "metricgroup.h"
 #include "parse-events.h"
@@ -320,7 +321,9 @@ void metricgroup__print(const struct print_callbacks *print_cb, void *print_stat
  */
 void print_events(const struct print_callbacks *print_cb, void *print_state)
 {
-	perf_pmus__print_pmu_events(print_cb, print_state);
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
+
+	perf_pmus__print_pmu_events(pmus, print_cb, print_state);
 
 	print_cb->print_event(print_state,
 			/*topic=*/NULL,
@@ -335,7 +338,7 @@ void print_events(const struct print_callbacks *print_cb, void *print_state)
 			/*long_desc=*/NULL,
 			/*encoding_desc=*/NULL);
 
-	perf_pmus__print_raw_pmu_events(print_cb, print_state);
+	perf_pmus__print_raw_pmu_events(pmus, print_cb, print_state);
 
 	print_cb->print_event(print_state,
 			/*topic=*/NULL,
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index 6ff8d97dcf7d..d22f6e8e504a 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -26,6 +26,7 @@
 #include "env.h"
 #include "evlist.h"
 #include "evsel.h"
+#include "pmu.h"
 #include "memswap.h"
 #include "map.h"
 #include "symbol.h"
@@ -264,6 +265,16 @@ void perf_session__delete(struct perf_session *session)
 	debuginfo_cache__delete();
 	perf_session__destroy_kernel_maps(session);
 	perf_decomp__release_events(session->decomp_data.decomp);
+	if (session->data && perf_data__is_read(session->data) && session->evlist) {
+		struct evsel *evsel;
+
+		evlist__for_each_entry(session->evlist, evsel) {
+			if (evsel->pmu && evsel->pmu->pmus == &session->header.env.pmus)
+				evsel->pmu = NULL;
+		}
+		if (evlist__session(session->evlist) == session)
+			evlist__set_session(session->evlist, NULL);
+	}
 	perf_env__exit(&session->header.env);
 	machines__exit(&session->machines);
 	if (session->data) {
diff --git a/tools/perf/util/stat-display.c b/tools/perf/util/stat-display.c
index b337cc23f413..288f98a323e9 100644
--- a/tools/perf/util/stat-display.c
+++ b/tools/perf/util/stat-display.c
@@ -770,7 +770,9 @@ static bool evlist__has_hybrid_pmus(struct evlist *evlist)
 	struct evsel *evsel;
 	struct perf_pmu *last_core_pmu = NULL;
 
-	if (perf_pmus__num_core_pmus() == 1)
+	struct perf_pmus *pmus = perf_env__pmus(evlist__env(evlist) ?: host_env());
+
+	if (perf_pmus__num_core_pmus(pmus) == 1)
 		return false;
 
 	evlist__for_each_entry(evlist, evsel) {
diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
index 525a3fe4a46e..f5ae399bf336 100644
--- a/tools/perf/util/stat-shadow.c
+++ b/tools/perf/util/stat-shadow.c
@@ -236,7 +236,8 @@ static void perf_stat__print_metricgroup_header(struct perf_stat_config *config,
 						const char *name,
 						struct perf_stat_output_ctx *out)
 {
-	bool need_full_name = perf_pmus__num_core_pmus() > 1;
+	bool need_full_name =
+		perf_pmus__num_core_pmus(perf_env__pmus(evsel__env(evsel) ?: host_env())) > 1;
 	static const char *last_name;
 	static const struct perf_pmu *last_pmu;
 	char full_name[64];
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 09/30] perf pmus: Pass struct perf_pmus to find helpers
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (7 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 08/30] perf pmus: Introduce struct perf_pmus and perf_env__pmus() Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:11   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 10/30] perf pmus: Pass struct perf_pmus to scan helpers Ian Rogers
                   ` (20 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Update perf_pmus__find(), perf_pmus__find_by_type(), and
perf_pmus__find_core_pmu() to take a struct perf_pmus * argument and
update callers to pass the appropriate environment's PMUs.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/arch/arm/util/auxtrace.c           |  9 ++--
 tools/perf/arch/arm/util/cs-etm.c             |  3 +-
 tools/perf/arch/arm64/util/pmu.c              |  3 +-
 tools/perf/arch/x86/tests/amd-ibs-period.c    |  5 +-
 .../arch/x86/tests/amd-ibs-via-core-pmu.c     |  3 +-
 tools/perf/arch/x86/tests/hybrid.c            |  3 +-
 tools/perf/arch/x86/tests/topdown.c           |  3 +-
 tools/perf/arch/x86/util/auxtrace.c           |  9 ++--
 tools/perf/arch/x86/util/evsel.c              |  6 ++-
 tools/perf/arch/x86/util/intel-bts.c          |  4 +-
 tools/perf/arch/x86/util/intel-pt.c           |  4 +-
 tools/perf/arch/x86/util/topdown.c            |  3 +-
 tools/perf/bench/pmu-scan.c                   |  3 +-
 tools/perf/builtin-stat.c                     |  8 +--
 tools/perf/tests/parse-events.c               | 14 ++---
 tools/perf/trace/beauty/perf_event_open.c     |  4 +-
 tools/perf/util/aslr.c                        |  7 ++-
 tools/perf/util/env.c                         |  2 +-
 tools/perf/util/evsel.c                       |  6 ++-
 tools/perf/util/evsel_fprintf.c               |  6 ++-
 tools/perf/util/evsel_fprintf.h               |  4 +-
 tools/perf/util/header.c                      |  8 +--
 tools/perf/util/metricgroup.c                 | 51 ++++++++++++-------
 tools/perf/util/metricgroup.h                 |  4 +-
 tools/perf/util/parse-events.c                | 18 ++++---
 .../perf/util/perf-regs-arch/perf_regs_x86.c  |  2 +-
 tools/perf/util/perf_event_attr_fprintf.c     |  7 +--
 tools/perf/util/pfm.c                         |  6 ++-
 tools/perf/util/pmus.c                        | 28 ++++------
 tools/perf/util/pmus.h                        | 27 ++++++++--
 tools/perf/util/s390-sample-raw.c             | 16 ++++--
 31 files changed, 176 insertions(+), 100 deletions(-)

diff --git a/tools/perf/arch/arm/util/auxtrace.c b/tools/perf/arch/arm/util/auxtrace.c
index 27bb14c8b880..f3a4f292a2c7 100644
--- a/tools/perf/arch/arm/util/auxtrace.c
+++ b/tools/perf/arch/arm/util/auxtrace.c
@@ -13,6 +13,7 @@
 
 #include "../../../util/auxtrace.h"
 #include "../../../util/debug.h"
+#include "../../../util/env.h"
 #include "../../../util/evlist.h"
 #include "../../../util/pmu.h"
 #include "../../../util/pmus.h"
@@ -22,6 +23,7 @@
 
 static struct perf_pmu **find_all_arm_spe_pmus(int *nr_spes, int *err)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu **arm_spe_pmus = NULL;
 	int ret, i, nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
 	/* arm_spe_xxxxxxxxx\0 */
@@ -42,7 +44,7 @@ static struct perf_pmu **find_all_arm_spe_pmus(int *nr_spes, int *err)
 			return NULL;
 		}
 
-		arm_spe_pmus[*nr_spes] = perf_pmus__find(arm_spe_pmu_name);
+		arm_spe_pmus[*nr_spes] = perf_pmus__find(pmus, arm_spe_pmu_name);
 		if (arm_spe_pmus[*nr_spes]) {
 			pr_debug2("%s %d: arm_spe_pmu %d type %d name %s\n",
 				 __func__, __LINE__, *nr_spes,
@@ -57,6 +59,7 @@ static struct perf_pmu **find_all_arm_spe_pmus(int *nr_spes, int *err)
 
 static struct perf_pmu **find_all_hisi_ptt_pmus(int *nr_ptts, int *err)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu **hisi_ptt_pmus = NULL;
 	struct dirent *dent;
 	char path[PATH_MAX];
@@ -89,7 +92,7 @@ static struct perf_pmu **find_all_hisi_ptt_pmus(int *nr_ptts, int *err)
 	rewinddir(dir);
 	while ((dent = readdir(dir))) {
 		if (strstr(dent->d_name, HISI_PTT_PMU_NAME) && idx < *nr_ptts) {
-			hisi_ptt_pmus[idx] = perf_pmus__find(dent->d_name);
+			hisi_ptt_pmus[idx] = perf_pmus__find(pmus, dent->d_name);
 			if (hisi_ptt_pmus[idx])
 				idx++;
 		}
@@ -133,7 +136,7 @@ struct auxtrace_record
 	if (!evlist)
 		return NULL;
 
-	cs_etm_pmu = perf_pmus__find(CORESIGHT_ETM_PMU_NAME);
+	cs_etm_pmu = perf_pmus__find(perf_env__pmus(host_env()), CORESIGHT_ETM_PMU_NAME);
 	arm_spe_pmus = find_all_arm_spe_pmus(&nr_spes, err);
 	hisi_ptt_pmus = find_all_hisi_ptt_pmus(&nr_ptts, err);
 
diff --git a/tools/perf/arch/arm/util/cs-etm.c b/tools/perf/arch/arm/util/cs-etm.c
index d2861d66a661..e15bd87f65fb 100644
--- a/tools/perf/arch/arm/util/cs-etm.c
+++ b/tools/perf/arch/arm/util/cs-etm.c
@@ -25,6 +25,7 @@
 #include "../../../util/auxtrace.h"
 #include "../../../util/cpumap.h"
 #include "../../../util/debug.h"
+#include "../../../util/env.h"
 #include "../../../util/event.h"
 #include "../../../util/evlist.h"
 #include "../../../util/evsel.h"
@@ -883,7 +884,7 @@ struct auxtrace_record *cs_etm_record_init(int *err)
 	struct perf_pmu *cs_etm_pmu;
 	struct cs_etm_recording *ptr;
 
-	cs_etm_pmu = perf_pmus__find(CORESIGHT_ETM_PMU_NAME);
+	cs_etm_pmu = perf_pmus__find(perf_env__pmus(host_env()), CORESIGHT_ETM_PMU_NAME);
 
 	if (!cs_etm_pmu) {
 		*err = -EINVAL;
diff --git a/tools/perf/arch/arm64/util/pmu.c b/tools/perf/arch/arm64/util/pmu.c
index 895fb0d0610c..798b423fe149 100644
--- a/tools/perf/arch/arm64/util/pmu.c
+++ b/tools/perf/arch/arm64/util/pmu.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
+#include "../../../util/env.h"
 #include "../../../util/pmu.h"
 #include "../../../util/pmus.h"
 #include "../../../util/tool_pmu.h"
@@ -9,7 +10,7 @@ u64 tool_pmu__cpu_slots_per_cycle(void)
 {
 	char path[PATH_MAX];
 	unsigned long long slots = 0;
-	struct perf_pmu *pmu = perf_pmus__find_core_pmu();
+	struct perf_pmu *pmu = perf_pmus__find_core_pmu(perf_env__pmus(host_env()));
 
 	if (pmu) {
 		perf_pmu__pathname_scnprintf(path, sizeof(path),
diff --git a/tools/perf/arch/x86/tests/amd-ibs-period.c b/tools/perf/arch/x86/tests/amd-ibs-period.c
index 32713f8fcd5c..4a32eaea61c7 100644
--- a/tools/perf/arch/x86/tests/amd-ibs-period.c
+++ b/tools/perf/arch/x86/tests/amd-ibs-period.c
@@ -930,6 +930,7 @@ static bool kernel_v6_15_or_newer(void)
 int test__amd_ibs_period(struct test_suite *test __maybe_unused,
 			 int subtest __maybe_unused)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	char perf[PATH_MAX] = {'\0'};
 	int ret = TEST_OK;
 
@@ -940,8 +941,8 @@ int test__amd_ibs_period(struct test_suite *test __maybe_unused,
 	 * of the test to fail if kernel changes it after reading it here.
 	 */
 	perf_event_max_sample_rate = get_perf_event_max_sample_rate();
-	fetch_pmu = perf_pmus__find("ibs_fetch");
-	op_pmu = perf_pmus__find("ibs_op");
+	fetch_pmu = perf_pmus__find(pmus, "ibs_fetch");
+	op_pmu = perf_pmus__find(pmus, "ibs_op");
 
 	if (!x86__is_amd_cpu() || !fetch_pmu || !op_pmu)
 		return TEST_SKIP;
diff --git a/tools/perf/arch/x86/tests/amd-ibs-via-core-pmu.c b/tools/perf/arch/x86/tests/amd-ibs-via-core-pmu.c
index 78b1902f6f59..a509a861b4f5 100644
--- a/tools/perf/arch/x86/tests/amd-ibs-via-core-pmu.c
+++ b/tools/perf/arch/x86/tests/amd-ibs-via-core-pmu.c
@@ -2,6 +2,7 @@
 #include "arch-tests.h"
 #include "linux/perf_event.h"
 #include "tests/tests.h"
+#include "env.h"
 #include "pmu.h"
 #include "pmus.h"
 #include "../perf-sys.h"
@@ -44,7 +45,7 @@ int test__amd_ibs_via_core_pmu(struct test_suite *test __maybe_unused,
 	int ret = TEST_OK;
 	int fd, i;
 
-	ibs_pmu = perf_pmus__find("ibs_op");
+	ibs_pmu = perf_pmus__find(perf_env__pmus(host_env()), "ibs_op");
 	if (!ibs_pmu)
 		return TEST_SKIP;
 
diff --git a/tools/perf/arch/x86/tests/hybrid.c b/tools/perf/arch/x86/tests/hybrid.c
index 011217998a3e..0146cc2e0c5b 100644
--- a/tools/perf/arch/x86/tests/hybrid.c
+++ b/tools/perf/arch/x86/tests/hybrid.c
@@ -112,10 +112,11 @@ static int test__hybrid_group_modifier1(struct evlist *evlist)
 
 static int test__hybrid_raw1(struct evlist *evlist)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_evsel *evsel;
 
 	perf_evlist__for_each_evsel(evlist__core(evlist), evsel) {
-		struct perf_pmu *pmu = perf_pmus__find_by_type(evsel->attr.type);
+		struct perf_pmu *pmu = perf_pmus__find_by_type(pmus, evsel->attr.type);
 
 		TEST_ASSERT_VAL("missing pmu", pmu);
 		TEST_ASSERT_VAL("unexpected pmu", !strncmp(pmu->name, "cpu_", 4));
diff --git a/tools/perf/arch/x86/tests/topdown.c b/tools/perf/arch/x86/tests/topdown.c
index 520075329c37..827169b60813 100644
--- a/tools/perf/arch/x86/tests/topdown.c
+++ b/tools/perf/arch/x86/tests/topdown.c
@@ -5,6 +5,7 @@
 #include "arch-tests.h"
 #include "../util/topdown.h"
 #include "debug.h"
+#include "env.h"
 #include "evlist.h"
 #include "parse-events.h"
 #include "pmu.h"
@@ -245,7 +246,7 @@ static int test__x86_topdown_metrics_clear(struct test_suite *test __maybe_unuse
 	if (!topdown_sys_has_perf_metrics())
 		return TEST_OK;
 
-	pmu = perf_pmus__find_by_type(PERF_TYPE_RAW);
+	pmu = perf_pmus__find_by_type(perf_env__pmus(host_env()), PERF_TYPE_RAW);
 	if (!pmu || !perf_pmu__has_format(pmu, "metrics_clear"))
 		return TEST_OK;
 
diff --git a/tools/perf/arch/x86/util/auxtrace.c b/tools/perf/arch/x86/util/auxtrace.c
index 84fce0b51ccf..540caf06d15c 100644
--- a/tools/perf/arch/x86/util/auxtrace.c
+++ b/tools/perf/arch/x86/util/auxtrace.c
@@ -9,6 +9,7 @@
 
 #include "../../../util/header.h"
 #include "../../../util/debug.h"
+#include "../../../util/env.h"
 #include "../../../util/pmu.h"
 #include "../../../util/pmus.h"
 #include "../../../util/auxtrace.h"
@@ -20,15 +21,13 @@ static
 struct auxtrace_record *auxtrace_record__init_intel(struct evlist *evlist,
 						    int *err)
 {
-	struct perf_pmu *intel_pt_pmu;
-	struct perf_pmu *intel_bts_pmu;
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
+	struct perf_pmu *intel_pt_pmu = perf_pmus__find(pmus, INTEL_PT_PMU_NAME);
+	struct perf_pmu *intel_bts_pmu = perf_pmus__find(pmus, INTEL_BTS_PMU_NAME);
 	struct evsel *evsel;
 	bool found_pt = false;
 	bool found_bts = false;
 
-	intel_pt_pmu = perf_pmus__find(INTEL_PT_PMU_NAME);
-	intel_bts_pmu = perf_pmus__find(INTEL_BTS_PMU_NAME);
-
 	evlist__for_each_entry(evlist, evsel) {
 		if (intel_pt_pmu && evsel->core.attr.type == intel_pt_pmu->type)
 			found_pt = true;
diff --git a/tools/perf/arch/x86/util/evsel.c b/tools/perf/arch/x86/util/evsel.c
index 23a8e662a912..a8e239d29b72 100644
--- a/tools/perf/arch/x86/util/evsel.c
+++ b/tools/perf/arch/x86/util/evsel.c
@@ -132,6 +132,7 @@ static void ibs_l3miss_warn(void)
 
 void arch__post_evsel_config(struct evsel *evsel, struct perf_event_attr *attr)
 {
+	struct perf_pmus *pmus;
 	struct perf_pmu *evsel_pmu, *ibs_fetch_pmu, *ibs_op_pmu;
 	static int warned_once;
 
@@ -142,8 +143,9 @@ void arch__post_evsel_config(struct evsel *evsel, struct perf_event_attr *attr)
 	if (!evsel_pmu)
 		return;
 
-	ibs_fetch_pmu = perf_pmus__find("ibs_fetch");
-	ibs_op_pmu = perf_pmus__find("ibs_op");
+	pmus = perf_env__pmus(host_env());
+	ibs_fetch_pmu = perf_pmus__find(pmus, "ibs_fetch");
+	ibs_op_pmu = perf_pmus__find(pmus, "ibs_op");
 
 	if (ibs_fetch_pmu && ibs_fetch_pmu->type == evsel_pmu->type) {
 		if (attr->config & IBS_FETCH_L3MISSONLY) {
diff --git a/tools/perf/arch/x86/util/intel-bts.c b/tools/perf/arch/x86/util/intel-bts.c
index d44d568a6d21..2a7b20f5a1ca 100644
--- a/tools/perf/arch/x86/util/intel-bts.c
+++ b/tools/perf/arch/x86/util/intel-bts.c
@@ -19,6 +19,7 @@
 #include "../../../util/auxtrace.h"
 #include "../../../util/cpumap.h"
 #include "../../../util/debug.h"
+#include "../../../util/env.h"
 #include "../../../util/event.h"
 #include "../../../util/evlist.h"
 #include "../../../util/evsel.h"
@@ -420,7 +421,8 @@ static int intel_bts_find_snapshot(struct auxtrace_record *itr, int idx,
 
 struct auxtrace_record *intel_bts_recording_init(int *err)
 {
-	struct perf_pmu *intel_bts_pmu = perf_pmus__find(INTEL_BTS_PMU_NAME);
+	struct perf_pmu *intel_bts_pmu = perf_pmus__find(perf_env__pmus(host_env()),
+							 INTEL_BTS_PMU_NAME);
 	struct intel_bts_recording *btsr;
 
 	if (!intel_bts_pmu)
diff --git a/tools/perf/arch/x86/util/intel-pt.c b/tools/perf/arch/x86/util/intel-pt.c
index a533114c0048..7756971ade50 100644
--- a/tools/perf/arch/x86/util/intel-pt.c
+++ b/tools/perf/arch/x86/util/intel-pt.c
@@ -23,6 +23,7 @@
 #include "../../../util/config.h"
 #include "../../../util/cpumap.h"
 #include "../../../util/debug.h"
+#include "../../../util/env.h"
 #include "../../../util/event.h"
 #include "../../../util/evlist.h"
 #include "../../../util/evsel.h"
@@ -1195,7 +1196,8 @@ static int intel_pt_perf_config(const char *var, const char *value, void *data)
 
 struct auxtrace_record *intel_pt_recording_init(int *err)
 {
-	struct perf_pmu *intel_pt_pmu = perf_pmus__find(INTEL_PT_PMU_NAME);
+	struct perf_pmu *intel_pt_pmu = perf_pmus__find(perf_env__pmus(host_env()),
+							INTEL_PT_PMU_NAME);
 	struct intel_pt_recording *ptr;
 
 	if (!intel_pt_pmu)
diff --git a/tools/perf/arch/x86/util/topdown.c b/tools/perf/arch/x86/util/topdown.c
index 64bc64e335a2..797ace8999e4 100644
--- a/tools/perf/arch/x86/util/topdown.c
+++ b/tools/perf/arch/x86/util/topdown.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <errno.h>
+#include "util/env.h"
 #include "util/evlist.h"
 #include "util/pmu.h"
 #include "util/pmus.h"
@@ -26,7 +27,7 @@ bool topdown_sys_has_perf_metrics(void)
 	 * The slots event is only available when the core PMU
 	 * supports the perf metrics feature.
 	 */
-	pmu = perf_pmus__find_by_type(PERF_TYPE_RAW);
+	pmu = perf_pmus__find_by_type(perf_env__pmus(host_env()), PERF_TYPE_RAW);
 	if (pmu && perf_pmu__have_event(pmu, "slots"))
 		has_perf_metrics = true;
 
diff --git a/tools/perf/bench/pmu-scan.c b/tools/perf/bench/pmu-scan.c
index 1d7dd4b203e0..754486d1d87b 100644
--- a/tools/perf/bench/pmu-scan.c
+++ b/tools/perf/bench/pmu-scan.c
@@ -77,6 +77,7 @@ static int save_result(void)
 
 static int check_result(bool core_only)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct pmu_scan_result *r;
 	struct perf_pmu *pmu;
 	struct list_head *list;
@@ -87,7 +88,7 @@ static int check_result(bool core_only)
 		if (core_only && !r->is_core)
 			continue;
 
-		pmu = perf_pmus__find(r->name);
+		pmu = perf_pmus__find(pmus, r->name);
 		if (pmu == NULL) {
 			pr_err("Cannot find PMU %s\n", r->name);
 			return -1;
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index 8706798290eb..f81192cec481 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -1982,6 +1982,7 @@ static int default_evlist_evsel_cmp(void *priv __maybe_unused,
 static int add_default_events(void)
 {
 	const char *pmu = parse_events_option_args.pmu_filter ?: "all";
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct parse_events_error err;
 	struct evlist *evlist = evlist__new();
 	struct evsel *evsel;
@@ -2002,7 +2003,7 @@ static int add_default_events(void)
 		 * will use this approach. To determine transaction support
 		 * on an architecture test for such a metric name.
 		 */
-		if (!metricgroup__has_metric_or_groups(pmu, "transaction")) {
+		if (!metricgroup__has_metric_or_groups(pmus, pmu, "transaction")) {
 			pr_err("Missing transaction metrics\n");
 			ret = -1;
 			goto out;
@@ -2037,7 +2038,7 @@ static int add_default_events(void)
 			smi_reset = true;
 		}
 
-		if (!metricgroup__has_metric_or_groups(pmu, "smi")) {
+		if (!metricgroup__has_metric_or_groups(pmus, pmu, "smi")) {
 			pr_err("Missing smi metrics\n");
 			ret = -1;
 			goto out;
@@ -2113,7 +2114,8 @@ static int add_default_events(void)
 		for (size_t i = 0; i < ARRAY_SIZE(default_metricgroup_names); i++) {
 			struct evlist *metric_evlist;
 
-			if (!metricgroup__has_metric_or_groups(pmu, default_metricgroup_names[i]))
+			if (!metricgroup__has_metric_or_groups(pmus, pmu,
+							       default_metricgroup_names[i]))
 				continue;
 
 			if ((int)i > detailed_run)
diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c
index 8969bcf004ea..c95f6d6f5854 100644
--- a/tools/perf/tests/parse-events.c
+++ b/tools/perf/tests/parse-events.c
@@ -641,7 +641,7 @@ static int test__checkevent_pmu(struct evlist *evlist)
 {
 
 	struct evsel *evsel = evlist__first(evlist);
-	struct perf_pmu *core_pmu = perf_pmus__find_core_pmu();
+	struct perf_pmu *core_pmu = perf_pmus__find_core_pmu(perf_env__pmus(host_env()));
 
 	TEST_ASSERT_EVSEL("wrong number of entries", 1 == evlist__nr_entries(evlist), evsel);
 	TEST_ASSERT_EVSEL("wrong type", core_pmu->type == evsel->core.attr.type, evsel);
@@ -705,7 +705,7 @@ static int test__checkevent_list(struct evlist *evlist)
 static int test__checkevent_pmu_name(struct evlist *evlist)
 {
 	struct evsel *evsel = evlist__first(evlist);
-	struct perf_pmu *core_pmu = perf_pmus__find_core_pmu();
+	struct perf_pmu *core_pmu = perf_pmus__find_core_pmu(perf_env__pmus(host_env()));
 	char buf[256];
 
 	/* default_core/config=1,name=krava/u */
@@ -729,7 +729,7 @@ static int test__checkevent_pmu_name(struct evlist *evlist)
 static int test__checkevent_pmu_partial_time_callgraph(struct evlist *evlist)
 {
 	struct evsel *evsel = evlist__first(evlist);
-	struct perf_pmu *core_pmu = perf_pmus__find_core_pmu();
+	struct perf_pmu *core_pmu = perf_pmus__find_core_pmu(perf_env__pmus(host_env()));
 
 	/* default_core/config=1,call-graph=fp,time,period=100000/ */
 	TEST_ASSERT_EVLIST("wrong number of entries",
@@ -762,7 +762,7 @@ static int test__checkevent_pmu_partial_time_callgraph(struct evlist *evlist)
 static int test__checkevent_pmu_events(struct evlist *evlist)
 {
 	struct evsel *evsel;
-	struct perf_pmu *core_pmu = perf_pmus__find_core_pmu();
+	struct perf_pmu *core_pmu = perf_pmus__find_core_pmu(perf_env__pmus(host_env()));
 
 	TEST_ASSERT_EVLIST("wrong number of entries", 1 <= evlist__nr_entries(evlist), evlist);
 
@@ -1768,7 +1768,7 @@ static int test__checkevent_config_cache(struct evlist *evlist)
 
 static bool test__pmu_default_core_event_valid(void)
 {
-	struct perf_pmu *pmu = perf_pmus__find_core_pmu();
+	struct perf_pmu *pmu = perf_pmus__find_core_pmu(perf_env__pmus(host_env()));
 
 	if (!pmu)
 		return false;
@@ -1778,7 +1778,7 @@ static bool test__pmu_default_core_event_valid(void)
 
 static bool test__intel_pt_valid(void)
 {
-	return !!perf_pmus__find("intel_pt");
+	return !!perf_pmus__find(perf_env__pmus(host_env()), "intel_pt");
 }
 
 static int test__intel_pt(struct evlist *evlist)
@@ -2624,7 +2624,7 @@ static int combine_test_results(int existing, int latest)
 static int test_events(const struct evlist_test *events, int cnt)
 {
 	int ret = TEST_OK;
-	struct perf_pmu *core_pmu = perf_pmus__find_core_pmu();
+	struct perf_pmu *core_pmu = perf_pmus__find_core_pmu(perf_env__pmus(host_env()));
 
 	for (int i = 0; i < cnt; i++) {
 		struct evlist_test e = events[i];
diff --git a/tools/perf/trace/beauty/perf_event_open.c b/tools/perf/trace/beauty/perf_event_open.c
index bfdf322c6ede..edf156995be6 100644
--- a/tools/perf/trace/beauty/perf_event_open.c
+++ b/tools/perf/trace/beauty/perf_event_open.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: LGPL-2.1
 #include <string.h>
 #include "trace/beauty/beauty.h"
+#include "util/env.h"
 #include "util/evsel_fprintf.h"
 #include <linux/perf_event.h>
 
@@ -75,7 +76,8 @@ static size_t perf_event_attr___scnprintf(struct perf_event_attr *attr, char *bf
 		.bf	 = bf,
 	};
 
-	perf_event_attr__fprintf(stdout, attr, attr__fprintf, &args);
+	perf_event_attr__fprintf(stdout, attr, attr__fprintf, &args,
+				 perf_env__pmus(host_env()));
 	return args.printed + scnprintf(bf + args.printed, size - args.printed, " }");
 }
 
diff --git a/tools/perf/util/aslr.c b/tools/perf/util/aslr.c
index e365f807b021..60928c490eb1 100644
--- a/tools/perf/util/aslr.c
+++ b/tools/perf/util/aslr.c
@@ -3,6 +3,7 @@
 
 #include "addr_location.h"
 #include "debug.h"
+#include "env.h"
 #include "event.h"
 #include "evsel.h"
 #include "evlist.h"
@@ -1255,9 +1256,10 @@ void aslr_tool__strip_attr_event(union perf_event *event, struct evlist *evlist)
 		    attr_size >= (offsetof(struct perf_event_attr, bp_addr) + sizeof(u64))) {
 			event->attr.attr.bp_addr = 0;
 		} else if (type >= PERF_TYPE_MAX) {
+			struct perf_env *env = evlist__env(evlist) ?: host_env();
 			struct perf_pmu *pmu;
 
-			pmu = perf_pmus__find_by_type(type);
+			pmu = perf_pmus__find_by_type(perf_env__pmus(env), type);
 			if (pmu && (!strcmp(pmu->name, "kprobe") ||
 				    !strcmp(pmu->name, "uprobe"))) {
 				if (attr_size >= (offsetof(struct perf_event_attr, config1) + sizeof(u64)))
@@ -1407,6 +1409,7 @@ int aslr_tool__cache_orig_attrs(struct perf_tool *tool, struct evsel *evsel)
 
 void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, struct evlist *evlist)
 {
+	struct perf_pmus *pmus = perf_env__pmus(evlist__env(evlist) ?: host_env());
 	struct evsel *evsel;
 
 	evlist__for_each_entry(evlist, evsel) {
@@ -1419,7 +1422,7 @@ void aslr_tool__strip_evlist(const struct perf_tool *tool __maybe_unused, struct
 		if (evsel->core.attr.type == PERF_TYPE_BREAKPOINT) {
 			evsel->core.attr.bp_addr = 0;
 		} else if (evsel->core.attr.type >= PERF_TYPE_MAX) {
-			struct perf_pmu *pmu = perf_pmus__find_by_type(evsel->core.attr.type);
+			struct perf_pmu *pmu = perf_pmus__find_by_type(pmus, evsel->core.attr.type);
 
 			if (pmu && (!strcmp(pmu->name, "kprobe") ||
 				    !strcmp(pmu->name, "uprobe"))) {
diff --git a/tools/perf/util/env.c b/tools/perf/util/env.c
index 5f16920f0a89..4f4ec49b6a6b 100644
--- a/tools/perf/util/env.c
+++ b/tools/perf/util/env.c
@@ -616,7 +616,7 @@ int perf_env__read_core_pmu_caps(struct perf_env *env)
 		return -ENODEV;
 
 	if (nr_pmu == 1) {
-		pmu = perf_pmus__find_core_pmu();
+		pmu = perf_pmus__find_core_pmu(pmus);
 		if (!pmu)
 			return -ENODEV;
 		ret = perf_pmu__caps_parse(pmu);
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 2fa8a0036375..9d58b9bfa8b6 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -2472,7 +2472,8 @@ static void display_attr(struct perf_event_attr *attr)
 	if (verbose >= 2 || debug_peo_args) {
 		fprintf(stderr, "%.60s\n", graph_dotted_line);
 		fprintf(stderr, "perf_event_attr:\n");
-		perf_event_attr__fprintf(stderr, attr, __open_attr__fprintf, NULL);
+		perf_event_attr__fprintf(stderr, attr, __open_attr__fprintf, NULL,
+					 perf_env__pmus(host_env()));
 		fprintf(stderr, "%.60s\n", graph_dotted_line);
 	}
 }
@@ -4317,7 +4318,8 @@ bool evsel__fallback(struct evsel *evsel, struct target *target, int err,
 		 * available even if no PMU support. PPC returned ENXIO rather
 		 * than ENODEV or ENOENT until 2.6.37.
 		 */
-		evsel->pmu = perf_pmus__find_by_type(PERF_TYPE_SOFTWARE);
+		evsel->pmu = perf_pmus__find_by_type(perf_env__pmus(host_env()),
+						     PERF_TYPE_SOFTWARE);
 		assert(evsel->pmu); /* software is a "well-known" and can't fail PMU type. */
 
 		/* Configure the event. */
diff --git a/tools/perf/util/evsel_fprintf.c b/tools/perf/util/evsel_fprintf.c
index 0f7a25500a44..ed7d13461575 100644
--- a/tools/perf/util/evsel_fprintf.c
+++ b/tools/perf/util/evsel_fprintf.c
@@ -2,6 +2,7 @@
 #include <inttypes.h>
 #include <stdio.h>
 #include <stdbool.h>
+#include "util/env.h"
 #include "util/evlist.h"
 #include "evsel.h"
 #include "util/evsel_fprintf.h"
@@ -66,8 +67,11 @@ int evsel__fprintf(struct evsel *evsel, struct perf_attr_details *details, FILE
 	printed += fprintf(fp, "%s", evsel__name(evsel));
 
 	if (details->verbose) {
+		struct perf_env *env = evsel__env(evsel) ?: host_env();
+
 		printed += perf_event_attr__fprintf(fp, &evsel->core.attr,
-						    __print_attr__fprintf, &first);
+						    __print_attr__fprintf, &first,
+						    perf_env__pmus(env));
 	} else if (details->freq) {
 		const char *term = "sample_freq";
 
diff --git a/tools/perf/util/evsel_fprintf.h b/tools/perf/util/evsel_fprintf.h
index c8a9fac2f2dd..792437ceb578 100644
--- a/tools/perf/util/evsel_fprintf.h
+++ b/tools/perf/util/evsel_fprintf.h
@@ -6,6 +6,7 @@
 #include <stdbool.h>
 
 struct evsel;
+struct perf_pmus;
 
 struct perf_attr_details {
 	bool freq;
@@ -46,5 +47,6 @@ int sample__fprintf_sym(struct perf_sample *sample, struct addr_location *al,
 typedef int (*attr__fprintf_f)(FILE *, const char *, const char *, void *);
 
 int perf_event_attr__fprintf(FILE *fp, struct perf_event_attr *attr,
-			     attr__fprintf_f attr__fprintf, void *priv);
+			     attr__fprintf_f attr__fprintf, void *priv,
+			     struct perf_pmus *pmus);
 #endif // __PERF_EVSEL_H
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
index b6f86f557caf..48ff46c165fd 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -1704,7 +1704,7 @@ static int __write_pmu_caps(struct feat_fd *ff, struct perf_pmu *pmu,
 static int write_cpu_pmu_caps(struct feat_fd *ff,
 			      struct evlist *evlist __maybe_unused)
 {
-	struct perf_pmu *cpu_pmu = perf_pmus__find_core_pmu();
+	struct perf_pmu *cpu_pmu = perf_pmus__find_core_pmu(perf_env__pmus(host_env()));
 	int ret;
 
 	if (!cpu_pmu)
@@ -2492,6 +2492,7 @@ static int __desc_attr__fprintf(FILE *fp, const char *name, const char *val,
 
 static void print_event_desc(struct feat_fd *ff, FILE *fp)
 {
+	struct perf_pmus *pmus = perf_env__pmus(&ff->ph->env);
 	struct evsel *evsel, *events;
 	u32 j;
 	u64 *id;
@@ -2519,7 +2520,7 @@ static void print_event_desc(struct feat_fd *ff, FILE *fp)
 			fprintf(fp, " }");
 		}
 
-		perf_event_attr__fprintf(fp, &evsel->core.attr, __desc_attr__fprintf, NULL);
+		perf_event_attr__fprintf(fp, &evsel->core.attr, __desc_attr__fprintf, NULL, pmus);
 
 		fputc('\n', fp);
 	}
@@ -5735,7 +5736,8 @@ size_t perf_event__fprintf_event_update(union perf_event *event, FILE *fp)
 
 size_t perf_event__fprintf_attr(union perf_event *event, FILE *fp)
 {
-	return perf_event_attr__fprintf(fp, &event->attr.attr, __desc_attr__fprintf, NULL);
+	return perf_event_attr__fprintf(fp, &event->attr.attr, __desc_attr__fprintf, NULL,
+					perf_env__pmus(host_env()));
 }
 
 int perf_event__process_attr(const struct perf_tool *tool __maybe_unused,
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 63aa326944d7..593032197edd 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -388,14 +388,14 @@ static bool match_metric_or_groups(const char *metric_or_groups, const char *sou
 	return m && match_metric_or_groups(m + 1, sought);
 }
 
-static bool match_pm_metric_or_groups(const struct pmu_metric *pm, const char *pmu,
-				      const char *metric_or_groups)
+static bool match_pm_metric_or_groups(struct perf_pmus *pmus, const struct pmu_metric *pm,
+				      const char *pmu, const char *metric_or_groups)
 {
 	const char *pm_pmu = pm->pmu ?: "cpu";
 	struct perf_pmu *perf_pmu = NULL;
 
 	if (pm->pmu)
-		perf_pmu = perf_pmus__find(pm->pmu);
+		perf_pmu = perf_pmus__find(pmus, pm->pmu);
 
 	if (strcmp(pmu, "all") && strcmp(pm_pmu, pmu) &&
 	   (perf_pmu && !perf_pmu__name_wildcard_match(perf_pmu, pmu)))
@@ -697,6 +697,7 @@ struct metricgroup_add_iter_data {
 };
 
 static int add_metric(struct list_head *metric_list,
+		      struct perf_pmus *pmus,
 		      const struct pmu_metric *pm,
 		      const char *modifier,
 		      bool metric_no_group,
@@ -722,6 +723,7 @@ static int metricgroup__find_metric_callback(const struct pmu_metric *pm,
  * resolve_metric - Locate metrics within the root metric and recursively add
  *                    references to them.
  * @metric_list: The list the metric is added to.
+ * @pmus: The PMUs collection for resolving referenced metrics.
  * @pmu: The PMU name to resolve metrics on, or "all" for all PMUs.
  * @modifier: if non-null event modifiers like "u".
  * @metric_no_group: Should events written to events be grouped "{}" or
@@ -739,6 +741,7 @@ static int metricgroup__find_metric_callback(const struct pmu_metric *pm,
  *       architecture perf is running upon.
  */
 static int resolve_metric(struct list_head *metric_list,
+			  struct perf_pmus *pmus,
 			  struct perf_pmu *pmu,
 			  const char *modifier,
 			  bool metric_no_group,
@@ -793,7 +796,7 @@ static int resolve_metric(struct list_head *metric_list,
 	 * context.
 	 */
 	for (i = 0; i < pending_cnt; i++) {
-		ret = add_metric(metric_list, &pending[i].pm, modifier, metric_no_group,
+		ret = add_metric(metric_list, pmus, &pending[i].pm, modifier, metric_no_group,
 				 metric_no_threshold, user_requested_cpu_list, system_wide,
 				 fake_pmu, root_metric, visited, table);
 		if (ret)
@@ -807,6 +810,7 @@ static int resolve_metric(struct list_head *metric_list,
 /**
  * __add_metric - Add a metric to metric_list.
  * @metric_list: The list the metric is added to.
+ * @pmus: The PMUs collection for resolving referenced metrics.
  * @pm: The pmu_metric containing the metric to be added.
  * @modifier: if non-null event modifiers like "u".
  * @metric_no_group: Should events written to events be grouped "{}" or
@@ -826,6 +830,7 @@ static int resolve_metric(struct list_head *metric_list,
  *       architecture perf is running upon.
  */
 static int __add_metric(struct list_head *metric_list,
+			struct perf_pmus *pmus,
 			const struct pmu_metric *pm,
 			const char *modifier,
 			bool metric_no_group,
@@ -927,11 +932,11 @@ static int __add_metric(struct list_head *metric_list,
 		struct perf_pmu *pmu;
 
 		if (pm->pmu && pm->pmu[0] != '\0')
-			pmu = perf_pmus__find(pm->pmu);
+			pmu = perf_pmus__find(pmus, pm->pmu);
 		else
 			pmu = perf_pmus__scan_core(/*pmu=*/ NULL);
 
-		ret = resolve_metric(metric_list, pmu, modifier, metric_no_group,
+		ret = resolve_metric(metric_list, pmus, pmu, modifier, metric_no_group,
 				     metric_no_threshold, user_requested_cpu_list,
 				     system_wide, fake_pmu, root_metric, &visited_node,
 				     table);
@@ -947,6 +952,7 @@ static int __add_metric(struct list_head *metric_list,
 }
 
 static int add_metric(struct list_head *metric_list,
+		      struct perf_pmus *pmus,
 		      const struct pmu_metric *pm,
 		      const char *modifier,
 		      bool metric_no_group,
@@ -963,7 +969,7 @@ static int add_metric(struct list_head *metric_list,
 	pr_debug("metric expr %s for %s\n", pm->metric_expr, pm->metric_name);
 
 	if (!strstr(pm->metric_expr, "?")) {
-		ret = __add_metric(metric_list, pm, modifier, metric_no_group,
+		ret = __add_metric(metric_list, pmus, pm, modifier, metric_no_group,
 				   metric_no_threshold, 0, user_requested_cpu_list,
 				   system_wide, fake_pmu, root_metric, visited, table);
 	} else {
@@ -977,7 +983,7 @@ static int add_metric(struct list_head *metric_list,
 		 */
 
 		for (j = 0; j < count && !ret; j++)
-			ret = __add_metric(metric_list, pm, modifier, metric_no_group,
+			ret = __add_metric(metric_list, pmus, pm, modifier, metric_no_group,
 					   metric_no_threshold, j, user_requested_cpu_list,
 					   system_wide, fake_pmu, root_metric, visited, table);
 	}
@@ -1033,6 +1039,7 @@ static int default_metricgroup_cmp(void *priv __maybe_unused,
 
 struct metricgroup__add_metric_data {
 	struct list_head *list;
+	struct perf_pmus *pmus;
 	const char *pmu;
 	const char *metric_name;
 	const char *modifier;
@@ -1051,14 +1058,15 @@ static int metricgroup__add_metric_callback(const struct pmu_metric *pm,
 	struct metricgroup__add_metric_data *data = vdata;
 	int ret = 0;
 
-	if (pm->metric_expr && match_pm_metric_or_groups(pm, data->pmu, data->metric_name)) {
+	if (pm->metric_expr && match_pm_metric_or_groups(data->pmus, pm, data->pmu,
+							 data->metric_name)) {
 		bool metric_no_group = data->metric_no_group ||
 			match_metric_or_groups(pm->metricgroup_no_group, data->metric_name);
 
 		data->has_match = true;
-		ret = add_metric(data->list, pm, data->modifier, metric_no_group,
+		ret = add_metric(data->list, data->pmus, pm, data->modifier, metric_no_group,
 				 data->metric_no_threshold, data->user_requested_cpu_list,
-				data->system_wide, data->fake_pmu, /*root_metric=*/NULL,
+				 data->system_wide, data->fake_pmu, /*root_metric=*/NULL,
 				 /*visited_metrics=*/NULL, table);
 	}
 	return ret;
@@ -1066,6 +1074,7 @@ static int metricgroup__add_metric_callback(const struct pmu_metric *pm,
 
 /**
  * metricgroup__add_metric - Find and add a metric, or a metric group.
+ * @pmus: The PMUs collection for resolving PMU names and referenced metrics.
  * @pmu: The PMU name to search for metrics on, or "all" for all PMUs.
  * @metric_name: The name of the metric or metric group. For example, "IPC"
  *               could be the name of a metric and "TopDownL1" the name of a
@@ -1081,7 +1090,8 @@ static int metricgroup__add_metric_callback(const struct pmu_metric *pm,
  * @table: The table that is searched for metrics, most commonly the table for the
  *       architecture perf is running upon.
  */
-static int metricgroup__add_metric(const char *pmu, const char *metric_name, const char *modifier,
+static int metricgroup__add_metric(struct perf_pmus *pmus, const char *pmu,
+				   const char *metric_name, const char *modifier,
 				   bool metric_no_group, bool metric_no_threshold,
 				   const char *user_requested_cpu_list,
 				   bool system_wide, bool fake_pmu,
@@ -1092,6 +1102,7 @@ static int metricgroup__add_metric(const char *pmu, const char *metric_name, con
 	int ret;
 	struct metricgroup__add_metric_data data = {
 		.list = &list,
+		.pmus = pmus,
 		.pmu = pmu,
 		.metric_name = metric_name,
 		.modifier = modifier,
@@ -1122,6 +1133,7 @@ static int metricgroup__add_metric(const char *pmu, const char *metric_name, con
 /**
  * metricgroup__add_metric_list - Find and add metrics, or metric groups,
  *                                specified in a list.
+ * @pmus: The PMUs collection for resolving PMU names and referenced metrics.
  * @pmu: A pmu to restrict the metrics to, or "all" for all PMUS.
  * @list: the list of metrics or metric groups. For example, "IPC,CPI,TopDownL1"
  *        would match the IPC and CPI metrics, and TopDownL1 would match all
@@ -1136,7 +1148,8 @@ static int metricgroup__add_metric(const char *pmu, const char *metric_name, con
  * @table: The table that is searched for metrics, most commonly the table for the
  *       architecture perf is running upon.
  */
-static int metricgroup__add_metric_list(const char *pmu, const char *list,
+static int metricgroup__add_metric_list(struct perf_pmus *pmus, const char *pmu,
+					const char *list,
 					bool metric_no_group,
 					bool metric_no_threshold,
 					const char *user_requested_cpu_list,
@@ -1157,7 +1170,7 @@ static int metricgroup__add_metric_list(const char *pmu, const char *list,
 		if (modifier)
 			*modifier++ = '\0';
 
-		ret = metricgroup__add_metric(pmu, metric_name, modifier,
+		ret = metricgroup__add_metric(pmus, pmu, metric_name, modifier,
 					      metric_no_group, metric_no_threshold,
 					      user_requested_cpu_list,
 					      system_wide, fake_pmu,
@@ -1412,6 +1425,7 @@ static int parse_groups(struct evlist *perf_evlist,
 			bool fake_pmu,
 			const struct pmu_metrics_table *table)
 {
+	struct perf_pmus *pmus = perf_env__pmus(evlist__env(perf_evlist) ?: host_env());
 	struct evlist *combined_evlist = NULL;
 	LIST_HEAD(metric_list);
 	struct metric *m;
@@ -1419,7 +1433,7 @@ static int parse_groups(struct evlist *perf_evlist,
 	bool is_default = !strcmp(str, "Default");
 	int ret;
 
-	ret = metricgroup__add_metric_list(pmu, str, metric_no_group, metric_no_threshold,
+	ret = metricgroup__add_metric_list(pmus, pmu, str, metric_no_group, metric_no_threshold,
 					   user_requested_cpu_list,
 					   system_wide, fake_pmu,
 					   &metric_list, table);
@@ -1618,6 +1632,7 @@ int metricgroup__parse_groups_test(struct evlist *evlist,
 }
 
 struct metricgroup__has_metric_data {
+	struct perf_pmus *pmus;
 	const char *pmu;
 	const char *metric_or_groups;
 };
@@ -1628,13 +1643,15 @@ static int metricgroup__has_metric_or_groups_callback(const struct pmu_metric *p
 {
 	struct metricgroup__has_metric_data *data = vdata;
 
-	return match_pm_metric_or_groups(pm, data->pmu, data->metric_or_groups) ? 1 : 0;
+	return match_pm_metric_or_groups(data->pmus, pm, data->pmu, data->metric_or_groups) ? 1 : 0;
 }
 
-bool metricgroup__has_metric_or_groups(const char *pmu, const char *metric_or_groups)
+bool metricgroup__has_metric_or_groups(struct perf_pmus *pmus, const char *pmu,
+				       const char *metric_or_groups)
 {
 	const struct pmu_metrics_table *table = pmu_metrics_table__find();
 	struct metricgroup__has_metric_data data = {
+		.pmus = pmus,
 		.pmu = pmu,
 		.metric_or_groups = metric_or_groups,
 	};
diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
index 6a66f14dd01b..bc5d3d21f405 100644
--- a/tools/perf/util/metricgroup.h
+++ b/tools/perf/util/metricgroup.h
@@ -10,6 +10,7 @@
 struct evlist;
 struct evsel;
 struct option;
+struct perf_pmus;
 struct print_callbacks;
 struct rblist;
 struct cgroup;
@@ -86,7 +87,8 @@ int metricgroup__parse_groups_test(struct evlist *evlist,
 
 int metricgroup__for_each_metric(const struct pmu_metrics_table *table, pmu_metric_iter_fn fn,
 				 void *data);
-bool metricgroup__has_metric_or_groups(const char *pmu, const char *metric_or_groups);
+bool metricgroup__has_metric_or_groups(struct perf_pmus *pmus, const char *pmu,
+				       const char *metric_or_groups);
 unsigned int metricgroups__topdown_max_level(void);
 int arch_get_runtimeparam(const struct pmu_metric *pm);
 void metricgroup__rblist_init(struct rblist *metric_events);
diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c
index b56f0e2d1542..bb99588589f4 100644
--- a/tools/perf/util/parse-events.c
+++ b/tools/perf/util/parse-events.c
@@ -85,7 +85,7 @@ static char *get_config_name(const struct parse_events_terms *head_terms)
 }
 
 static struct perf_cpu_map *get_config_cpu(const struct parse_events_terms *head_terms,
-					   bool fake_pmu)
+					   struct parse_events_state *parse_state)
 {
 	struct parse_events_term *term;
 	struct perf_cpu_map *cpus = NULL;
@@ -102,7 +102,8 @@ static struct perf_cpu_map *get_config_cpu(const struct parse_events_terms *head
 		if (term->type_val == PARSE_EVENTS__TERM_TYPE_NUM) {
 			term_cpus = perf_cpu_map__new_int(term->val.num);
 		} else {
-			struct perf_pmu *pmu = perf_pmus__find(term->val.str);
+			struct perf_pmu *pmu = perf_pmus__find(parse_state->pmus,
+							       term->val.str);
 
 			if (pmu) {
 				term_cpus = pmu->is_core && perf_cpu_map__is_empty(pmu->cpus)
@@ -110,7 +111,7 @@ static struct perf_cpu_map *get_config_cpu(const struct parse_events_terms *head
 					    : perf_cpu_map__get(pmu->cpus);
 			} else {
 				term_cpus = perf_cpu_map__new(term->val.str);
-				if (!term_cpus && fake_pmu) {
+				if (!term_cpus && parse_state->fake_pmu) {
 					/*
 					 * Assume the PMU string makes sense on a different
 					 * machine and fake a value with all online CPUs.
@@ -929,7 +930,7 @@ do {											\
 			break;
 		}
 		assert(term->type_val == PARSE_EVENTS__TERM_TYPE_STR);
-		if (perf_pmus__find(term->val.str) != NULL)
+		if (perf_pmus__find(parse_state->pmus, term->val.str) != NULL)
 			break;
 
 		map = perf_cpu_map__new(term->val.str);
@@ -1429,7 +1430,7 @@ static int __parse_events_add_numeric(struct parse_events_state *parse_state,
 
 	name = get_config_name(head_config);
 	metric_id = get_config_metric_id(head_config);
-	cpus = get_config_cpu(head_config, parse_state->fake_pmu);
+	cpus = get_config_cpu(head_config, parse_state);
 	ret = __add_event(list, &parse_state->idx, &attr, /*init_attr*/true, name,
 			metric_id, parse_state->pmus, pmu, &config_terms,
 			first_wildcard_match, cpus,
@@ -1471,7 +1472,8 @@ int parse_events_add_numeric(struct parse_events_state *parse_state,
 		if (found_supported)
 			return 0;
 	}
-	return __parse_events_add_numeric(parse_state, list, perf_pmus__find_by_type(type),
+	return __parse_events_add_numeric(parse_state, list,
+					perf_pmus__find_by_type(parse_state->pmus, type),
 					type, /*extended_type=*/0, config, head_config,
 					/*first_wildcard_match=*/NULL);
 }
@@ -1592,7 +1594,7 @@ static int parse_events_add_pmu(struct parse_events_state *parse_state,
 		return -EINVAL;
 	}
 
-	term_cpu = get_config_cpu(&parsed_terms, parse_state->fake_pmu);
+	term_cpu = get_config_cpu(&parsed_terms, parse_state);
 	evsel = __add_event(list, &parse_state->idx, &attr, /*init_attr=*/true,
 			    get_config_name(&parsed_terms),
 			    get_config_metric_id(&parsed_terms), parse_state->pmus, pmu,
@@ -1730,7 +1732,7 @@ int parse_events_multi_pmu_add_or_add_pmu(struct parse_events_state *parse_state
 	INIT_LIST_HEAD(*listp);
 
 	/* Attempt to add to list assuming event_or_pmu is a PMU name. */
-	pmu = perf_pmus__find(event_or_pmu);
+	pmu = perf_pmus__find(parse_state->pmus, event_or_pmu);
 	if (pmu && !parse_events_add_pmu(parse_state, *listp, pmu, const_parsed_terms,
 					 first_wildcard_match))
 		return 0;
diff --git a/tools/perf/util/perf-regs-arch/perf_regs_x86.c b/tools/perf/util/perf-regs-arch/perf_regs_x86.c
index 2d80f119629a..920f17b89659 100644
--- a/tools/perf/util/perf-regs-arch/perf_regs_x86.c
+++ b/tools/perf/util/perf-regs-arch/perf_regs_x86.c
@@ -534,7 +534,7 @@ __support_simd_reg_class(uint64_t sample_type, uint16_t qwords,
 	}
 
 	if (perf_pmus__num_core_pmus(pmus) > 1) {
-		__u64 type = perf_pmus__find_core_pmu()->type;
+		__u64 type = perf_pmus__find_core_pmu(pmus)->type;
 
 		attr.config |= type << PERF_PMU_TYPE_SHIFT;
 	}
diff --git a/tools/perf/util/perf_event_attr_fprintf.c b/tools/perf/util/perf_event_attr_fprintf.c
index 2e8be9a357dc..7fd87c0e19f6 100644
--- a/tools/perf/util/perf_event_attr_fprintf.c
+++ b/tools/perf/util/perf_event_attr_fprintf.c
@@ -298,9 +298,10 @@ do {									\
 #define PRINT_ATTRf_bf(_f, _p)	PRINT_ATTRn_bf(#_f, _f, _p, false)
 
 int perf_event_attr__fprintf(FILE *fp, struct perf_event_attr *attr,
-			     attr__fprintf_f attr__fprintf, void *priv)
+			     attr__fprintf_f attr__fprintf, void *priv,
+			     struct perf_pmus *pmus)
 {
-	struct perf_pmu *pmu = perf_pmus__find_by_type(attr->type);
+	struct perf_pmu *pmu = perf_pmus__find_by_type(pmus, attr->type);
 	/*
 	 * size == 0 means ABI0 — the producer didn't set attr.size.
 	 * perf_event__fprintf_attr() may pass the raw mmap'd event
@@ -328,7 +329,7 @@ int perf_event_attr__fprintf(FILE *fp, struct perf_event_attr *attr,
 		u32 extended_type = attr->config >> PERF_PMU_TYPE_SHIFT;
 
 		if (extended_type)
-			pmu = perf_pmus__find_by_type(extended_type);
+			pmu = perf_pmus__find_by_type(pmus, extended_type);
 	}
 
 	PRINT_ATTRn("type", type, p_type_id, true);
diff --git a/tools/perf/util/pfm.c b/tools/perf/util/pfm.c
index ad9bc481da70..c761e701a41d 100644
--- a/tools/perf/util/pfm.c
+++ b/tools/perf/util/pfm.c
@@ -6,6 +6,7 @@
  */
 #include "util/cpumap.h"
 #include "util/debug.h"
+#include "util/env.h"
 #include "util/event.h"
 #include "util/evlist.h"
 #include "util/evsel.h"
@@ -35,6 +36,7 @@ int parse_libpfm_events_option(const struct option *opt, const char *str,
 			int unset __maybe_unused)
 {
 	struct evlist *evlist = *(struct evlist **)opt->value;
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_event_attr attr;
 	struct perf_pmu *pmu;
 	struct evsel *evsel, *grp_leader = NULL;
@@ -84,7 +86,7 @@ int parse_libpfm_events_option(const struct option *opt, const char *str,
 			goto error;
 		}
 
-		pmu = perf_pmus__find_by_type((unsigned int)attr.type);
+		pmu = perf_pmus__find_by_type(pmus, (unsigned int)attr.type);
 		evsel = parse_events__add_event(evlist__nr_entries(evlist),
 						&attr, q, /*metric_id=*/NULL,
 						pmu);
@@ -135,7 +137,7 @@ static bool is_libpfm_event_supported(const char *name, struct perf_cpu_map *cpu
 	if (ret != PFM_SUCCESS)
 		return false;
 
-	pmu = perf_pmus__find_by_type((unsigned int)attr.type);
+	pmu = perf_pmus__find_by_type(perf_env__pmus(host_env()), (unsigned int)attr.type);
 	evsel = parse_events__add_event(0, &attr, name, /*metric_id=*/NULL, pmu);
 	if (evsel == NULL)
 		return false;
diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
index 3adb297636b6..903ea3d559e7 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -159,7 +159,7 @@ static struct perf_pmu *pmu_find(struct perf_pmus *pmus, const char *name)
 	return NULL;
 }
 
-static struct perf_pmu *__perf_pmus__find(struct perf_pmus *pmus, const char *name)
+struct perf_pmu *perf_pmus__find(struct perf_pmus *pmus, const char *name)
 {
 	struct perf_pmu *pmu;
 	int dirfd;
@@ -167,7 +167,7 @@ static struct perf_pmu *__perf_pmus__find(struct perf_pmus *pmus, const char *na
 	unsigned int to_read_pmus = 0;
 
 	if (!strcmp(name, "default_core"))
-		return perf_pmus__find_core_pmu();
+		return perf_pmus__find_core_pmu(pmus);
 	/*
 	 * Once PMU is loaded it stays in the list,
 	 * so we keep us from multiple reading/parsing
@@ -216,11 +216,6 @@ static struct perf_pmu *__perf_pmus__find(struct perf_pmus *pmus, const char *na
 	return pmu_find(pmus, name);
 }
 
-struct perf_pmu *perf_pmus__find(const char *name)
-{
-	return __perf_pmus__find(perf_env__pmus(host_env()), name);
-}
-
 static struct perf_pmu *perf_pmu__find2(struct perf_pmus *pmus, int dirfd, const char *name)
 {
 	struct perf_pmu *pmu;
@@ -340,7 +335,7 @@ static struct perf_pmu *__perf_pmus__find_by_type(struct perf_pmus *pmus, unsign
 	return NULL;
 }
 
-static struct perf_pmu *pmu_find_by_type(struct perf_pmus *pmus, unsigned int type)
+struct perf_pmu *perf_pmus__find_by_type(struct perf_pmus *pmus, unsigned int type)
 {
 	unsigned int to_read_pmus;
 	struct perf_pmu *pmu = __perf_pmus__find_by_type(pmus, type);
@@ -363,11 +358,6 @@ static struct perf_pmu *pmu_find_by_type(struct perf_pmus *pmus, unsigned int ty
 	return pmu;
 }
 
-struct perf_pmu *perf_pmus__find_by_type(unsigned int type)
-{
-	return pmu_find_by_type(perf_env__pmus(host_env()), type);
-}
-
 /*
  * pmu iterator: If pmu is NULL, we start at the begin, otherwise return the
  * next pmu. Returns NULL on end.
@@ -852,7 +842,7 @@ void perf_pmus__print_raw_pmu_events(struct perf_pmus *pmus,
 
 bool perf_pmus__have_event(struct perf_pmus *pmus, const char *pname, const char *name)
 {
-	struct perf_pmu *pmu = __perf_pmus__find(pmus, pname);
+	struct perf_pmu *pmu = perf_pmus__find(pmus, pname);
 
 	return pmu && perf_pmu__have_event(pmu, name);
 }
@@ -915,14 +905,14 @@ bool perf_pmus__host_supports_extended_type(void)
 struct perf_pmu *perf_pmus__find_by_attr(struct perf_pmus *pmus,
 					 const struct perf_event_attr *attr)
 {
-	struct perf_pmu *pmu = pmu_find_by_type(pmus, attr->type);
+	struct perf_pmu *pmu = perf_pmus__find_by_type(pmus, attr->type);
 	u32 type = attr->type;
 	bool legacy_core_type = type == PERF_TYPE_HARDWARE || type == PERF_TYPE_HW_CACHE;
 
 	if (!pmu && legacy_core_type && perf_pmus__supports_extended_type(pmus)) {
 		type = attr->config >> PERF_PMU_TYPE_SHIFT;
 
-		pmu = pmu_find_by_type(pmus, type);
+		pmu = perf_pmus__find_by_type(pmus, type);
 	}
 	if (!pmu && (legacy_core_type || type == PERF_TYPE_RAW)) {
 		/*
@@ -933,7 +923,7 @@ struct perf_pmu *perf_pmus__find_by_attr(struct perf_pmus *pmus,
 		 * PERF_TYPE_RAW, assume the RAW events are going to be handled
 		 * by the first core PMU.
 		 */
-		pmu = pmu_scan_core(pmus, NULL);
+		pmu = perf_pmus__find_core_pmu(pmus);
 	}
 	return pmu;
 }
@@ -952,9 +942,9 @@ struct perf_pmu *evsel__find_pmu(const struct evsel *evsel)
 	return pmu;
 }
 
-struct perf_pmu *perf_pmus__find_core_pmu(void)
+struct perf_pmu *perf_pmus__find_core_pmu(struct perf_pmus *pmus)
 {
-	return perf_pmus__scan_core(NULL);
+	return pmu_scan_core(pmus, NULL);
 }
 
 struct perf_pmu *perf_pmus__add_test_pmu(struct perf_pmus *pmus,
diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h
index 45f4bd8b6aa6..cd0c14f8e04b 100644
--- a/tools/perf/util/pmus.h
+++ b/tools/perf/util/pmus.h
@@ -75,8 +75,23 @@ void perf_pmus__init(struct perf_pmus *pmus);
  */
 void perf_pmus__exit(struct perf_pmus *pmus);
 
-struct perf_pmu *perf_pmus__find(const char *name);
-struct perf_pmu *perf_pmus__find_by_type(unsigned int type);
+/**
+ * perf_pmus__find() - Find a PMU by name in @pmus.
+ * @pmus: The PMU collection to search.
+ * @name: Name of the PMU to look up.
+ *
+ * Return: Pointer to the matching struct perf_pmu, or NULL if not found.
+ */
+struct perf_pmu *perf_pmus__find(struct perf_pmus *pmus, const char *name);
+
+/**
+ * perf_pmus__find_by_type() - Find a PMU by its perf_event_attr type ID.
+ * @pmus: The PMU collection to search.
+ * @type: The PMU type identifier (e.g. PERF_TYPE_RAW or dynamic sysfs type).
+ *
+ * Return: Pointer to the matching struct perf_pmu, or NULL if not found.
+ */
+struct perf_pmu *perf_pmus__find_by_type(struct perf_pmus *pmus, unsigned int type);
 
 /**
  * perf_pmus__find_by_attr() - Find the PMU corresponding to @attr.
@@ -217,6 +232,12 @@ struct perf_pmu *perf_pmus__add_test_hwmon_pmu(struct perf_pmus *pmus,
  * Return: Pointer to the fake struct perf_pmu.
  */
 struct perf_pmu *perf_pmus__fake_pmu(void);
-struct perf_pmu *perf_pmus__find_core_pmu(void);
+/**
+ * perf_pmus__find_core_pmu() - Return the first core PMU in @pmus.
+ * @pmus: The PMU collection to search.
+ *
+ * Return: Pointer to the first core struct perf_pmu, or NULL if none exists.
+ */
+struct perf_pmu *perf_pmus__find_core_pmu(struct perf_pmus *pmus);
 
 #endif /* __PMUS_H */
diff --git a/tools/perf/util/s390-sample-raw.c b/tools/perf/util/s390-sample-raw.c
index aaf8838b2cb2..650dfcba794a 100644
--- a/tools/perf/util/s390-sample-raw.c
+++ b/tools/perf/util/s390-sample-raw.c
@@ -24,6 +24,7 @@
 
 #include "color.h"
 #include "debug.h"
+#include "env.h"
 #include "evlist.h"
 #include "hashmap.h"
 #include "pmu.h"
@@ -352,8 +353,11 @@ void evlist__s390_sample_raw(struct evlist *evlist, union perf_event *event,
 
 	/* Display raw data on screen */
 	if (sample->evsel->core.attr.config == PERF_EVENT_CPUM_CF_DIAG) {
-		if (!sample->evsel->pmu)
-			sample->evsel->pmu = perf_pmus__find("cpum_cf");
+		if (!sample->evsel->pmu) {
+			struct perf_env *env = evsel__env(sample->evsel) ?: host_env();
+
+			sample->evsel->pmu = perf_pmus__find(perf_env__pmus(env), "cpum_cf");
+		}
 		if (!s390_cpumcfdg_testctr(sample))
 			pr_err("Invalid counter set data encountered\n");
 		else
@@ -375,8 +379,12 @@ void evlist__s390_sample_raw(struct evlist *evlist, union perf_event *event,
 	if (!s390_pai_all_test(sample)) {
 		pr_err("Invalid %s raw data encountered\n", pai_name);
 	} else {
-		if (!sample->evsel->pmu)
-			sample->evsel->pmu = perf_pmus__find_by_type(sample->evsel->core.attr.type);
+		if (!sample->evsel->pmu) {
+			struct perf_env *env = evsel__env(sample->evsel) ?: host_env();
+
+			sample->evsel->pmu = perf_pmus__find_by_type(perf_env__pmus(env),
+								     sample->evsel->core.attr.type);
+		}
 		s390_pai_all_dump(sample);
 	}
 }
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 10/30] perf pmus: Pass struct perf_pmus to scan helpers
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (8 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 09/30] perf pmus: Pass struct perf_pmus to find helpers Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:06   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 11/30] perf pmu-events: Add e_machine to struct pmu_events_map Ian Rogers
                   ` (19 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Update perf_pmus__scan() and perf_pmus__scan_core() to take a
struct perf_pmus * argument and update callers to pass the appropriate
environment's PMUs.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/arch/x86/tests/topdown.c           |  3 +-
 tools/perf/bench/pmu-scan.c                   |  6 ++--
 tools/perf/tests/event_groups.c               |  4 ++-
 tools/perf/tests/mmap-basic.c                 |  2 +-
 tools/perf/tests/parse-events.c               |  9 +++--
 tools/perf/tests/pmu-events.c                 |  4 ++-
 tools/perf/tests/uncore-event-sorting.c       |  6 ++--
 tools/perf/util/cputopo.c                     |  2 +-
 tools/perf/util/env.c                         |  7 ++--
 tools/perf/util/evlist.c                      |  3 +-
 tools/perf/util/header.c                      | 10 +++---
 tools/perf/util/mem-events.c                  |  5 ++-
 tools/perf/util/metricgroup.c                 |  2 +-
 tools/perf/util/parse-events.c                | 28 ++++++++--------
 .../perf/util/perf-regs-arch/perf_regs_x86.c  |  2 +-
 tools/perf/util/perf_api_probe.c              |  3 +-
 tools/perf/util/pmu.c                         |  4 ++-
 tools/perf/util/pmus.c                        | 33 +++++++------------
 tools/perf/util/pmus.h                        | 19 +++++++++--
 tools/perf/util/python.c                      |  4 +--
 20 files changed, 92 insertions(+), 64 deletions(-)

diff --git a/tools/perf/arch/x86/tests/topdown.c b/tools/perf/arch/x86/tests/topdown.c
index 827169b60813..fec51274e76e 100644
--- a/tools/perf/arch/x86/tests/topdown.c
+++ b/tools/perf/arch/x86/tests/topdown.c
@@ -65,13 +65,14 @@ static int event_cb(void *state, struct pmu_event_info *info)
 
 static int test__x86_topdown(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	int ret = TEST_OK;
 	struct perf_pmu *pmu = NULL;
 
 	if (!topdown_sys_has_perf_metrics())
 		return TEST_OK;
 
-	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL) {
 		if (perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false, &ret, event_cb))
 			break;
 	}
diff --git a/tools/perf/bench/pmu-scan.c b/tools/perf/bench/pmu-scan.c
index 754486d1d87b..fe58fbfcf96d 100644
--- a/tools/perf/bench/pmu-scan.c
+++ b/tools/perf/bench/pmu-scan.c
@@ -48,7 +48,7 @@ static int save_result(void)
 	struct list_head *list;
 	struct pmu_scan_result *r;
 
-	while ((pmu = perf_pmus__scan(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan(pmus, pmu)) != NULL) {
 		r = realloc(results, (nr_pmus + 1) * sizeof(*r));
 		if (r == NULL)
 			return -ENOMEM;
@@ -153,9 +153,9 @@ static int run_pmu_scan(void)
 		for (unsigned int i = 0; i < iterations; i++) {
 			gettimeofday(&start, NULL);
 			if (core_only)
-				perf_pmus__scan_core(NULL);
+				perf_pmus__scan_core(pmus, NULL);
 			else
-				perf_pmus__scan(NULL);
+				perf_pmus__scan(pmus, NULL);
 			gettimeofday(&end, NULL);
 			timersub(&end, &start, &diff);
 			runtime_us = diff.tv_sec * USEC_PER_SEC + diff.tv_usec;
diff --git a/tools/perf/tests/event_groups.c b/tools/perf/tests/event_groups.c
index c119ff114948..f52d2fb08820 100644
--- a/tools/perf/tests/event_groups.c
+++ b/tools/perf/tests/event_groups.c
@@ -5,6 +5,7 @@
 #include "linux/perf_event.h"
 #include "tests.h"
 #include "debug.h"
+#include "env.h"
 #include "pmu.h"
 #include "pmus.h"
 #include "header.h"
@@ -51,10 +52,11 @@ static int event_open(int type, unsigned long config, int group_fd)
 
 static int setup_uncore_event(void)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 	int i, fd;
 
-	while ((pmu = perf_pmus__scan(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan(pmus, pmu)) != NULL) {
 		for (i = 0; i < NR_UNCORE_PMUS; i++) {
 			if (!strcmp(uncore_pmus[i].name, pmu->name)) {
 				pr_debug("Using %s for uncore pmu event\n", pmu->name);
diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c
index dada4af5fb68..a542aadb46a0 100644
--- a/tools/perf/tests/mmap-basic.c
+++ b/tools/perf/tests/mmap-basic.c
@@ -277,7 +277,7 @@ static int test_stat_user_read(u64 event, enum user_read_state enabled)
 	}
 	perf_thread_map__set_pid(threads, 0, 0);
 
-	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL) {
 		enum user_read_state saved_user_read_state = set_user_read(pmu, enabled);
 		struct perf_event_attr attr = {
 			.type	= PERF_TYPE_HARDWARE,
diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c
index c95f6d6f5854..b1e72866e64a 100644
--- a/tools/perf/tests/parse-events.c
+++ b/tools/perf/tests/parse-events.c
@@ -142,6 +142,7 @@ static int test__checkevent_tracepoint_multi(struct evlist *evlist)
 
 static int test__checkevent_raw(struct evlist *evlist)
 {
+	struct perf_pmus *pmus __maybe_unused = perf_env__pmus(host_env());
 	struct evsel *evsel;
 	bool raw_type_match = false;
 
@@ -164,7 +165,7 @@ static int test__checkevent_raw(struct evlist *evlist)
 		 */
 		type_matched = raw_type_match = true;
 #else
-		while ((pmu = perf_pmus__scan(pmu)) != NULL) {
+		while ((pmu = perf_pmus__scan(pmus, pmu)) != NULL) {
 			if (pmu->type == evsel->core.attr.type) {
 				TEST_ASSERT_EVSEL("PMU type expected once", !type_matched, evsel);
 				type_matched = true;
@@ -1792,9 +1793,10 @@ static int test__intel_pt(struct evlist *evlist)
 
 static bool test__acr_valid(void)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 
-	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL) {
 		if (perf_pmu__has_format(pmu, "acr_mask"))
 			return true;
 	}
@@ -2703,10 +2705,11 @@ static int test__terms2(struct test_suite *test __maybe_unused, int subtest __ma
 
 static int test__pmu_events(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 	int ret = TEST_OK;
 
-	while ((pmu = perf_pmus__scan(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan(pmus, pmu)) != NULL) {
 		struct stat st;
 		char path[PATH_MAX];
 		char pmu_event[PATH_MAX];
diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c
index acf8e8ee1237..cb0c2b05c1e8 100644
--- a/tools/perf/tests/pmu-events.c
+++ b/tools/perf/tests/pmu-events.c
@@ -9,6 +9,7 @@
 #include <linux/kernel.h>
 #include <linux/zalloc.h>
 #include "debug.h"
+#include "env.h"
 #include "../pmu-events/pmu-events.h"
 #include <perf/evlist.h>
 #include "util/evlist.h"
@@ -726,10 +727,11 @@ static struct perf_pmu_test_pmu test_pmus[] = {
 static int test__aliases(struct test_suite *test __maybe_unused,
 			int subtest __maybe_unused)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 	unsigned long i;
 
-	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL) {
 		int count = 0;
 
 		if (list_empty(&pmu->format)) {
diff --git a/tools/perf/tests/uncore-event-sorting.c b/tools/perf/tests/uncore-event-sorting.c
index 7756777c54c2..99af4d88bb81 100644
--- a/tools/perf/tests/uncore-event-sorting.c
+++ b/tools/perf/tests/uncore-event-sorting.c
@@ -2,6 +2,7 @@
 #include <string.h>
 
 #include "debug.h"
+#include "env.h"
 #include "evlist.h"
 #include "parse-events.h"
 #include "pmu.h"
@@ -84,6 +85,7 @@ static int test__uncore_event_sorting(struct test_suite *test __maybe_unused,
 	struct evlist *evlist = NULL;
 	struct parse_events_error err;
 	struct evsel *evsel;
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 	char *pmu_prefix = NULL;
 	struct match_state m = { NULL, NULL };
@@ -92,7 +94,7 @@ static int test__uncore_event_sorting(struct test_suite *test __maybe_unused,
 
 	parse_events_error__init(&err);
 
-	while ((pmu = perf_pmus__scan(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan(pmus, pmu)) != NULL) {
 		size_t len;
 		struct perf_pmu *sibling;
 
@@ -104,7 +106,7 @@ static int test__uncore_event_sorting(struct test_suite *test __maybe_unused,
 			continue;
 
 		sibling = pmu;
-		while ((sibling = perf_pmus__scan(sibling)) != NULL) {
+		while ((sibling = perf_pmus__scan(pmus, sibling)) != NULL) {
 			if (sibling->is_core)
 				continue;
 			if (pmu_name_len_no_suffix(sibling->name) == len &&
diff --git a/tools/perf/util/cputopo.c b/tools/perf/util/cputopo.c
index 04ab4d010149..2e8e8754bb3f 100644
--- a/tools/perf/util/cputopo.c
+++ b/tools/perf/util/cputopo.c
@@ -483,7 +483,7 @@ struct hybrid_topology *hybrid_topology__new(void)
 		return NULL;
 
 	tp->nr = nr;
-	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL) {
 		if (load_hybrid_node(&tp->nodes[i], pmu)) {
 			hybrid_topology__delete(tp);
 			return NULL;
diff --git a/tools/perf/util/env.c b/tools/perf/util/env.c
index 4f4ec49b6a6b..fec6a5a8ecb6 100644
--- a/tools/perf/util/env.c
+++ b/tools/perf/util/env.c
@@ -499,11 +499,12 @@ int perf_env__read_cpu_topology_map(struct perf_env *env)
 
 int perf_env__read_pmu_mappings(struct perf_env *env)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 	u32 pmu_num = 0;
 	struct strbuf sb;
 
-	while ((pmu = perf_pmus__scan(pmu)))
+	while ((pmu = perf_pmus__scan(pmus, pmu)))
 		pmu_num++;
 
 	if (!pmu_num) {
@@ -514,7 +515,7 @@ int perf_env__read_pmu_mappings(struct perf_env *env)
 	if (strbuf_init(&sb, 128 * pmu_num) < 0)
 		return -ENOMEM;
 
-	while ((pmu = perf_pmus__scan(pmu))) {
+	while ((pmu = perf_pmus__scan(pmus, pmu))) {
 		if (strbuf_addf(&sb, "%u:%s", pmu->type, pmu->name) < 0)
 			goto error;
 		/* include a NULL character at the end */
@@ -633,7 +634,7 @@ int perf_env__read_core_pmu_caps(struct perf_env *env)
 	if (!pmu_caps)
 		return -ENOMEM;
 
-	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL) {
 		if (perf_pmu__caps_parse(pmu) <= 0)
 			continue;
 		ret = __perf_env__read_core_pmu_caps(pmu, &pmu_caps[i].nr_caps,
diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
index 4e641ccc176f..faf5ce62811b 100644
--- a/tools/perf/util/evlist.c
+++ b/tools/perf/util/evlist.c
@@ -128,10 +128,11 @@ struct evlist *evlist__new_default(const struct target *target, bool sample_call
 		if (err)
 			goto out_err;
 	} else {
+		struct perf_pmus *pmus = perf_env__pmus(host_env());
 		struct evsel *leader = NULL;
 
 		/* Fallback for cross-platform file analysis missing the topology header */
-		while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+		while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL) {
 			snprintf(buf, sizeof(buf), "%s/cycles/%s", pmu->name,
 				can_profile_kernel ? "P" : "Pu");
 			err = parse_event(evlist, buf);
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
index 48ff46c165fd..2c686095fe67 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -855,6 +855,7 @@ static int write_numa_topology(struct feat_fd *ff,
 static int write_pmu_mappings(struct feat_fd *ff,
 			      struct evlist *evlist __maybe_unused)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 	u32 pmu_num = 0;
 	int ret;
@@ -863,14 +864,14 @@ static int write_pmu_mappings(struct feat_fd *ff,
 	 * Do a first pass to count number of pmu to avoid lseek so this
 	 * works in pipe mode as well.
 	 */
-	while ((pmu = perf_pmus__scan(pmu)))
+	while ((pmu = perf_pmus__scan(pmus, pmu)))
 		pmu_num++;
 
 	ret = do_write(ff, &pmu_num, sizeof(pmu_num));
 	if (ret < 0)
 		return ret;
 
-	while ((pmu = perf_pmus__scan(pmu))) {
+	while ((pmu = perf_pmus__scan(pmus, pmu))) {
 		ret = do_write(ff, &pmu->type, sizeof(pmu->type));
 		if (ret < 0)
 			return ret;
@@ -1720,11 +1721,12 @@ static int write_cpu_pmu_caps(struct feat_fd *ff,
 static int write_pmu_caps(struct feat_fd *ff,
 			  struct evlist *evlist __maybe_unused)
 {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct perf_pmu *pmu = NULL;
 	int nr_pmu = 0;
 	int ret;
 
-	while ((pmu = perf_pmus__scan(pmu))) {
+	while ((pmu = perf_pmus__scan(pmus, pmu))) {
 		if (!strcmp(pmu->name, "cpu")) {
 			/*
 			 * The "cpu" PMU is special and covered by
@@ -1750,7 +1752,7 @@ static int write_pmu_caps(struct feat_fd *ff,
 	 * of perf_pmus__scan.
 	 */
 	pmu = NULL;
-	while ((pmu = perf_pmus__scan(pmu))) {
+	while ((pmu = perf_pmus__scan(pmus, pmu))) {
 		if (!strcmp(pmu->name, "cpu")) {
 			/* Skip as above. */
 			continue;
diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c
index 0cdcb47647bd..62543df0dca1 100644
--- a/tools/perf/util/mem-events.c
+++ b/tools/perf/util/mem-events.c
@@ -13,6 +13,7 @@
 #include "mem-events.h"
 #include "mem-info.h"
 #include "debug.h"
+#include "env.h"
 #include "evsel.h"
 #include "symbol.h"
 #include "pmu.h"
@@ -42,7 +43,9 @@ struct perf_mem_event *perf_pmu__mem_events_ptr(struct perf_pmu *pmu, int i)
 
 static struct perf_pmu *perf_pmus__scan_mem(struct perf_pmu *pmu)
 {
-	while ((pmu = perf_pmus__scan(pmu)) != NULL) {
+	struct perf_pmus *pmus = perf_env__pmus(host_env());
+
+	while ((pmu = perf_pmus__scan(pmus, pmu)) != NULL) {
 		if (pmu->mem_events)
 			return pmu;
 	}
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 593032197edd..80c2086555a8 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -934,7 +934,7 @@ static int __add_metric(struct list_head *metric_list,
 		if (pm->pmu && pm->pmu[0] != '\0')
 			pmu = perf_pmus__find(pmus, pm->pmu);
 		else
-			pmu = perf_pmus__scan_core(/*pmu=*/ NULL);
+			pmu = perf_pmus__scan_core(pmus, /*pmu=*/NULL);
 
 		ret = resolve_metric(metric_list, pmus, pmu, modifier, metric_no_group,
 				     metric_no_threshold, user_requested_cpu_list,
diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c
index bb99588589f4..159d847f44ce 100644
--- a/tools/perf/util/parse-events.c
+++ b/tools/perf/util/parse-events.c
@@ -985,16 +985,16 @@ do {											\
 }
 
 static bool check_pmu_is_core(__u32 type, const struct parse_events_term *term,
-			      struct parse_events_error *err)
+			      struct parse_events_state *parse_state)
 {
 	struct perf_pmu *pmu = NULL;
 
 	/* Avoid loading all PMUs with perf_pmus__find_by_type, just scan the core ones. */
-	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan_core(parse_state->pmus, pmu)) != NULL) {
 		if (pmu->type == type)
 			return true;
 	}
-	parse_events_error__handle(err, term->err_val,
+	parse_events_error__handle(parse_state->error, term->err_val,
 				strdup("needs a core PMU"),
 				NULL);
 	return false;
@@ -1013,7 +1013,7 @@ static int config_term_pmu(struct perf_event_attr *attr,
 						   NULL);
 			return -EINVAL;
 		}
-		if (!check_pmu_is_core(attr->type, term, parse_state->error))
+		if (!check_pmu_is_core(attr->type, term, parse_state))
 			return -EINVAL;
 		attr->config = term->val.num;
 		if (perf_pmus__supports_extended_type(parse_state->pmus))
@@ -1038,7 +1038,7 @@ static int config_term_pmu(struct perf_event_attr *attr,
 						   NULL);
 			return -EINVAL;
 		}
-		if (!check_pmu_is_core(attr->type, term, parse_state->error))
+		if (!check_pmu_is_core(attr->type, term, parse_state))
 			return -EINVAL;
 		attr->config = term->val.num;
 		if (perf_pmus__supports_extended_type(parse_state->pmus))
@@ -1452,7 +1452,7 @@ int parse_events_add_numeric(struct parse_events_state *parse_state,
 	/* Wildcards on numeric values are only supported by core PMUs. */
 	if (wildcard && perf_pmus__supports_extended_type(parse_state->pmus)) {
 		struct evsel *first_wildcard_match = NULL;
-		while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+		while ((pmu = perf_pmus__scan_core(parse_state->pmus, pmu)) != NULL) {
 			int ret;
 
 			found_supported = true;
@@ -1986,7 +1986,8 @@ int parse_events_terms(struct parse_events_terms *terms, const char *str)
 }
 
 static int evsel__compute_group_pmu_name(struct evsel *evsel,
-					  const struct list_head *head)
+					  const struct list_head *head,
+					  struct perf_pmus *pmus)
 {
 	struct evsel *leader = evsel__leader(evsel);
 	struct evsel *pos;
@@ -1999,7 +2000,7 @@ static int evsel__compute_group_pmu_name(struct evsel *evsel,
 		 * is a core PMU, but in heterogeneous systems this is
 		 * unknown. For now pick the first core PMU.
 		 */
-		pmu = perf_pmus__scan_core(NULL);
+		pmu = perf_pmus__scan_core(pmus, NULL);
 	}
 	if (!pmu) {
 		pr_debug("No PMU found for '%s'\n", evsel__name(evsel));
@@ -2019,7 +2020,7 @@ static int evsel__compute_group_pmu_name(struct evsel *evsel,
 
 		if (!leader_pmu) {
 			/* As with determining pmu above. */
-			leader_pmu = perf_pmus__scan_core(NULL);
+			leader_pmu = perf_pmus__scan_core(pmus, NULL);
 		}
 		/*
 		 * Starting with the leader, find the first event with a named
@@ -2038,7 +2039,7 @@ static int evsel__compute_group_pmu_name(struct evsel *evsel,
 				pos_pmu = evsel__find_pmu(pos);
 				if (!pos_pmu) {
 					/* As with determining pmu above. */
-					pos_pmu = perf_pmus__scan_core(NULL);
+					pos_pmu = perf_pmus__scan_core(pmus, NULL);
 				}
 				if (pos_pmu && !perf_pmu__is_software(pos_pmu)) {
 					group_pmu_name = pos_pmu->name;
@@ -2128,7 +2129,8 @@ int __weak arch_evlist__add_required_events(struct list_head *list __always_unus
 	return 0;
 }
 
-static int parse_events__sort_events_and_fix_groups(struct list_head *list)
+static int parse_events__sort_events_and_fix_groups(struct list_head *list,
+						    struct perf_pmus *pmus)
 {
 	int idx = 0, force_grouped_idx = -1;
 	struct evsel *pos, *cur_leader = NULL;
@@ -2151,7 +2153,7 @@ static int parse_events__sort_events_and_fix_groups(struct list_head *list)
 	list_for_each_entry(pos, list, core.node) {
 		const struct evsel *pos_leader = evsel__leader(pos);
 
-		ret = evsel__compute_group_pmu_name(pos, list);
+		ret = evsel__compute_group_pmu_name(pos, list, pmus);
 		if (ret)
 			return ret;
 
@@ -2322,7 +2324,7 @@ int __parse_events(struct evlist *evlist, const char *str,
 		return -1;
 	}
 
-	ret2 = parse_events__sort_events_and_fix_groups(&parse_state.list);
+	ret2 = parse_events__sort_events_and_fix_groups(&parse_state.list, parse_state.pmus);
 	if (ret2 < 0 && !ret)
 		ret = ret2;
 
diff --git a/tools/perf/util/perf-regs-arch/perf_regs_x86.c b/tools/perf/util/perf-regs-arch/perf_regs_x86.c
index 920f17b89659..8cb9dfa6c7d3 100644
--- a/tools/perf/util/perf-regs-arch/perf_regs_x86.c
+++ b/tools/perf/util/perf-regs-arch/perf_regs_x86.c
@@ -268,7 +268,7 @@ static uint64_t __arch__reg_mask(u64 sample_type, u64 mask, bool has_simd_regs)
 		 * The same register set is supported among different hybrid PMUs.
 		 * Only check the first available one.
 		 */
-		while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+		while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL) {
 			type = pmu->type;
 			break;
 		}
diff --git a/tools/perf/util/perf_api_probe.c b/tools/perf/util/perf_api_probe.c
index f61c4ec52827..7a8b99f31985 100644
--- a/tools/perf/util/perf_api_probe.c
+++ b/tools/perf/util/perf_api_probe.c
@@ -6,6 +6,7 @@
 #include <perf/cpumap.h>
 
 #include "cloexec.h"
+#include "env.h"
 #include "evlist.h"
 #include "evsel.h"
 #include "parse-events.h"
@@ -78,7 +79,7 @@ static bool perf_probe_api(setup_probe_fn_t fn)
 	if (!ret)
 		return true;
 
-	pmu = perf_pmus__scan_core(/*pmu=*/NULL);
+	pmu = perf_pmus__scan_core(perf_env__pmus(host_env()), /*pmu=*/NULL);
 	if (pmu) {
 		const char *try[] = {"cycles", "instructions", NULL};
 		char buf[256];
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index ee6a217dde91..06c4912d2f65 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -2071,6 +2071,7 @@ bool is_pmu_core(const char *name)
  */
 bool is_pmu_core_len(struct perf_env *env, const char *name, size_t len)
 {
+	struct perf_pmus *pmus;
 	struct perf_pmu *pmu = NULL;
 
 	if (env && env->hybrid_nodes) {
@@ -2097,7 +2098,8 @@ bool is_pmu_core_len(struct perf_env *env, const char *name, size_t len)
 		return false;
 	}
 
-	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+	pmus = perf_env__pmus(env ?: host_env());
+	while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL) {
 		if (strlen(pmu->name) == len && !strncmp(name, pmu->name, len))
 			return true;
 	}
diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
index 903ea3d559e7..6d68371107aa 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -362,7 +362,7 @@ struct perf_pmu *perf_pmus__find_by_type(struct perf_pmus *pmus, unsigned int ty
  * pmu iterator: If pmu is NULL, we start at the begin, otherwise return the
  * next pmu. Returns NULL on end.
  */
-static struct perf_pmu *pmu_scan(struct perf_pmus *pmus, struct perf_pmu *pmu)
+struct perf_pmu *perf_pmus__scan(struct perf_pmus *pmus, struct perf_pmu *pmu)
 {
 	bool use_core_pmus = !pmu || pmu->is_core;
 
@@ -382,12 +382,7 @@ static struct perf_pmu *pmu_scan(struct perf_pmus *pmus, struct perf_pmu *pmu)
 	return NULL;
 }
 
-struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu)
-{
-	return pmu_scan(perf_env__pmus(host_env()), pmu);
-}
-
-static struct perf_pmu *pmu_scan_core(struct perf_pmus *pmus, struct perf_pmu *pmu)
+struct perf_pmu *perf_pmus__scan_core(struct perf_pmus *pmus, struct perf_pmu *pmu)
 {
 	if (!pmu) {
 		pmu_read_sysfs(pmus, PERF_TOOL_PMU_TYPE_PE_CORE_MASK);
@@ -399,11 +394,6 @@ static struct perf_pmu *pmu_scan_core(struct perf_pmus *pmus, struct perf_pmu *p
 	return NULL;
 }
 
-struct perf_pmu *perf_pmus__scan_core(struct perf_pmu *pmu)
-{
-	return pmu_scan_core(perf_env__pmus(host_env()), pmu);
-}
-
 struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmus *pmus,
 					   struct perf_pmu *pmu, const char *event)
 {
@@ -492,7 +482,8 @@ struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmus *pmus,
 	return NULL;
 }
 
-static struct perf_pmu *pmu_scan_skip_duplicates(struct perf_pmus *pmus, struct perf_pmu *pmu)
+static struct perf_pmu *
+perf_pmus__scan_skip_duplicates(struct perf_pmus *pmus, struct perf_pmu *pmu)
 {
 	bool use_core_pmus = !pmu || pmu->is_core;
 	int last_pmu_name_len = 0;
@@ -549,7 +540,7 @@ const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(struct perf_pmus *pmus, con
 {
 	struct perf_pmu *pmu = NULL;
 
-	while ((pmu = pmu_scan(pmus, pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan(pmus, pmu)) != NULL) {
 		if (!strcmp(pmu->name, str))
 			return pmu;
 		/* Ignore "uncore_" prefix. */
@@ -682,9 +673,9 @@ void perf_pmus__print_pmu_events(struct perf_pmus *pmus,
 	struct perf_pmu *(*scan_fn)(struct perf_pmus *p, struct perf_pmu *cur);
 
 	if (skip_duplicate_pmus)
-		scan_fn = pmu_scan_skip_duplicates;
+		scan_fn = perf_pmus__scan_skip_duplicates;
 	else
-		scan_fn = pmu_scan;
+		scan_fn = perf_pmus__scan;
 
 	pmu = NULL;
 	len = 0;
@@ -794,9 +785,9 @@ void perf_pmus__print_raw_pmu_events(struct perf_pmus *pmus,
 	struct perf_pmu *pmu = NULL;
 
 	if (skip_duplicate_pmus)
-		scan_fn = pmu_scan_skip_duplicates;
+		scan_fn = perf_pmus__scan_skip_duplicates;
 	else
-		scan_fn = pmu_scan;
+		scan_fn = perf_pmus__scan;
 
 	while ((pmu = scan_fn(pmus, pmu)) != NULL) {
 		struct build_format_string_args format_args = {
@@ -852,7 +843,7 @@ int perf_pmus__num_core_pmus(struct perf_pmus *pmus)
 	if (!pmus->num_core_pmus) {
 		struct perf_pmu *pmu = NULL;
 
-		while ((pmu = pmu_scan_core(pmus, pmu)) != NULL)
+		while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL)
 			pmus->num_core_pmus++;
 	}
 	return pmus->num_core_pmus;
@@ -865,7 +856,7 @@ static bool __perf_pmus__supports_extended_type(struct perf_pmus *pmus)
 	if (perf_pmus__num_core_pmus(pmus) <= 1)
 		return false;
 
-	while ((pmu = pmu_scan_core(pmus, pmu)) != NULL) {
+	while ((pmu = perf_pmus__scan_core(pmus, pmu)) != NULL) {
 		if (!pmu->from_env &&
 		    !is_event_supported(PERF_TYPE_HARDWARE,
 					PERF_COUNT_HW_CPU_CYCLES |
@@ -944,7 +935,7 @@ struct perf_pmu *evsel__find_pmu(const struct evsel *evsel)
 
 struct perf_pmu *perf_pmus__find_core_pmu(struct perf_pmus *pmus)
 {
-	return pmu_scan_core(pmus, NULL);
+	return perf_pmus__scan_core(pmus, NULL);
 }
 
 struct perf_pmu *perf_pmus__add_test_pmu(struct perf_pmus *pmus,
diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h
index cd0c14f8e04b..6e7c9f857aaf 100644
--- a/tools/perf/util/pmus.h
+++ b/tools/perf/util/pmus.h
@@ -103,8 +103,23 @@ struct perf_pmu *perf_pmus__find_by_type(struct perf_pmus *pmus, unsigned int ty
 struct perf_pmu *perf_pmus__find_by_attr(struct perf_pmus *pmus,
 					 const struct perf_event_attr *attr);
 
-struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu);
-struct perf_pmu *perf_pmus__scan_core(struct perf_pmu *pmu);
+/**
+ * perf_pmus__scan() - Iterate over all PMUs (core then other) in @pmus.
+ * @pmus: The PMU collection to scan.
+ * @pmu: Previous PMU returned by perf_pmus__scan(), or NULL to start.
+ *
+ * Return: Next struct perf_pmu in @pmus, or NULL when iteration completes.
+ */
+struct perf_pmu *perf_pmus__scan(struct perf_pmus *pmus, struct perf_pmu *pmu);
+
+/**
+ * perf_pmus__scan_core() - Iterate over core PMUs in @pmus.
+ * @pmus: The PMU collection to scan.
+ * @pmu: Previous core PMU returned by perf_pmus__scan_core(), or NULL to start.
+ *
+ * Return: Next core struct perf_pmu in @pmus, or NULL when iteration completes.
+ */
+struct perf_pmu *perf_pmus__scan_core(struct perf_pmus *pmus, struct perf_pmu *pmu);
 
 /**
  * perf_pmus__scan_for_event() - Iterate over PMUs in @pmus that have @event.
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 42c587cd54a5..a41372cd6bcb 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -2162,7 +2162,7 @@ static PyObject *pyrf_pmu_iterator__new(PyTypeObject *type, PyObject *args __may
 	struct pyrf_pmu_iterator *itr = (void *)type->tp_alloc(type, 0);
 
 	if (itr != NULL)
-		itr->pmu = perf_pmus__scan(/*pmu=*/NULL);
+		itr->pmu = perf_pmus__scan(perf_env__pmus(host_env()), /*pmu=*/NULL);
 
 	return (PyObject *) itr;
 }
@@ -2187,7 +2187,7 @@ static PyObject *pyrf_pmu_iterator__iternext(PyObject *self)
 	if (ppmu) {
 		ppmu->pmu = itr->pmu;
 		// Advance iterator.
-		itr->pmu = perf_pmus__scan(itr->pmu);
+		itr->pmu = perf_pmus__scan(perf_env__pmus(host_env()), itr->pmu);
 	}
 	return (PyObject *)ppmu;
 }
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 11/30] perf pmu-events: Add e_machine to struct pmu_events_map
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (9 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 10/30] perf pmus: Pass struct perf_pmus to scan helpers Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:12   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 12/30] perf pmu-events: Pass e_machine to find_core_*_table() Ian Rogers
                   ` (18 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

The CPUID strings in the pmu-events mapfile.csv files have an
architecture specific format, such as "GenuineIntel-6-55-[56789ABCDEF]"
on x86 or a MIDR value like "0x00000000410fd0c0" on arm64. When perf is
built with JEVENTS_ARCH=all the tables of every architecture are
present and map_for_cpu() compared the host's CPUID against all of
them, even though only the host architecture's tables can match.

Record the ELF machine of each architecture's CPUIDs in struct
pmu_events_map, EM_NONE for the test and common tables, and have
map_for_cpu() only consider tables for the host's ELF machine. The 32
and 64-bit variants of x86 and PowerPC share tables.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/pmu-events/empty-pmu-events.c | 24 +++++++++++++++-
 tools/perf/pmu-events/jevents.py         | 35 +++++++++++++++++++++++-
 2 files changed, 57 insertions(+), 2 deletions(-)

diff --git a/tools/perf/pmu-events/empty-pmu-events.c b/tools/perf/pmu-events/empty-pmu-events.c
index 7df1552403e7..7428ffd4d70c 100644
--- a/tools/perf/pmu-events/empty-pmu-events.c
+++ b/tools/perf/pmu-events/empty-pmu-events.c
@@ -4,6 +4,8 @@
 #include <pmu-events/pmu-events.h>
 #include "util/header.h"
 #include "util/pmu.h"
+#include <dwarf-regs.h>
+#include <elf.h>
 #include <string.h>
 #include <stddef.h>
 
@@ -5419,6 +5421,8 @@ struct pmu_metrics_table {
 struct pmu_events_map {
 	const char *arch;
 	const char *cpuid;
+	/* The ELF machine of the architecture's CPUIDs, EM_NONE for test and common. */
+	uint16_t e_machine;
 	struct pmu_events_table event_table;
 	struct pmu_metrics_table metric_table;
 };
@@ -5431,6 +5435,7 @@ static const struct pmu_events_map pmu_events_map[] = {
 {
 	.arch = "common",
 	.cpuid = "common",
+	.e_machine = EM_NONE,
 	.event_table = {
 		.pmus = pmu_events__common,
 		.num_pmus = ARRAY_SIZE(pmu_events__common),
@@ -5444,6 +5449,7 @@ static const struct pmu_events_map pmu_events_map[] = {
 {
 	.arch = "testarch",
 	.cpuid = "testcpu",
+	.e_machine = EM_NONE,
 	.event_table = {
 		.pmus = pmu_events__test_soc_cpu,
 		.num_pmus = ARRAY_SIZE(pmu_events__test_soc_cpu),
@@ -5457,6 +5463,7 @@ static const struct pmu_events_map pmu_events_map[] = {
 {
 	.arch = 0,
 	.cpuid = 0,
+	.e_machine = EM_NONE,
 	.event_table = { 0, 0 },
 	.metric_table = { 0 },
 }
@@ -5769,6 +5776,20 @@ int pmu_metrics_table__find_metric(const struct pmu_metrics_table *table,
 	return PMU_METRICS__NOT_FOUND;
 }
 
+/*
+ * Is the map for the architecture of @e_machine? The 32 and 64-bit variants of
+ * an architecture share tables.
+ */
+static bool pmu_events_map__e_machine_matches(const struct pmu_events_map *map,
+					      uint16_t e_machine)
+{
+	if (e_machine == EM_386)
+		e_machine = EM_X86_64;
+	else if (e_machine == EM_PPC)
+		e_machine = EM_PPC64;
+	return map->e_machine == e_machine;
+}
+
 static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
 {
 	static struct {
@@ -5809,7 +5830,8 @@ static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
 				break;
 			}
 
-			if (!strcmp_cpuid_str(map->cpuid, cpuid))
+			if (pmu_events_map__e_machine_matches(map, EM_HOST) &&
+			    !strcmp_cpuid_str(map->cpuid, cpuid))
 				break;
 		}
 		free(last_map_search.cpuid);
diff --git a/tools/perf/pmu-events/jevents.py b/tools/perf/pmu-events/jevents.py
index 080e04b51b8c..db1b4749aec3 100755
--- a/tools/perf/pmu-events/jevents.py
+++ b/tools/perf/pmu-events/jevents.py
@@ -26,6 +26,15 @@ _sys_event_table_to_metric_table_mapping: Dict[str, str] = {}
 # JsonEvent. Architecture standard events are in json files in the top
 # f'{_args.starting_dir}/{_args.arch}' directory.
 _arch_std_events: Dict[str, "JsonEvent"] = {}
+# The ELF machine of the CPUIDs in each architecture's mapfile.csv.
+_arch_e_machine: Dict[str, str] = {
+    'arm64': 'EM_AARCH64',
+    'nds32': 'EM_NDS32',
+    'powerpc': 'EM_PPC64',
+    'riscv': 'EM_RISCV',
+    's390': 'EM_S390',
+    'x86': 'EM_X86_64',
+}
 # Events to write out when the table is closed
 _pending_events: list["JsonEvent"] = []
 # Name of events table to be written out
@@ -730,6 +739,8 @@ struct pmu_metrics_table {
 struct pmu_events_map {
 \tconst char *arch;
 \tconst char *cpuid;
+\t/* The ELF machine of the architecture's CPUIDs, EM_NONE for test and common. */
+\tuint16_t e_machine;
 \tstruct pmu_events_table event_table;
 \tstruct pmu_metrics_table metric_table;
 };
@@ -745,6 +756,7 @@ static const struct pmu_events_map pmu_events_map[] = {
       _args.output_file.write("""{
 \t.arch = "testarch",
 \t.cpuid = "testcpu",
+\t.e_machine = EM_NONE,
 \t.event_table = {
 \t\t.pmus = pmu_events__test_soc_cpu,
 \t\t.num_pmus = ARRAY_SIZE(pmu_events__test_soc_cpu),
@@ -760,6 +772,7 @@ static const struct pmu_events_map pmu_events_map[] = {
       _args.output_file.write("""{
 \t.arch = "common",
 \t.cpuid = "common",
+\t.e_machine = EM_NONE,
 \t.event_table = {
 \t\t.pmus = pmu_events__common,
 \t\t.num_pmus = ARRAY_SIZE(pmu_events__common),
@@ -772,6 +785,7 @@ static const struct pmu_events_map pmu_events_map[] = {
 },
 """)
     else:
+      e_machine = _arch_e_machine[arch]
       with open(f'{_args.starting_dir}/{arch}/mapfile.csv', encoding='utf-8') as csvfile:
         table = csv.reader(csvfile)
         first = True
@@ -798,6 +812,7 @@ static const struct pmu_events_map pmu_events_map[] = {
             _args.output_file.write(f"""{{
 \t.arch = "{arch}",
 \t.cpuid = "{cpuid}",
+\t.e_machine = {e_machine},
 \t.event_table = {{
 \t\t.pmus = {event_tblname},
 \t\t.num_pmus = {event_size}
@@ -814,6 +829,7 @@ static const struct pmu_events_map pmu_events_map[] = {
   _args.output_file.write("""{
 \t.arch = 0,
 \t.cpuid = 0,
+\t.e_machine = EM_NONE,
 \t.event_table = { 0, 0 },
 \t.metric_table = { 0 },
 }
@@ -1178,6 +1194,20 @@ int pmu_metrics_table__find_metric(const struct pmu_metrics_table *table,
 \treturn PMU_METRICS__NOT_FOUND;
 }
 
+/*
+ * Is the map for the architecture of @e_machine? The 32 and 64-bit variants of
+ * an architecture share tables.
+ */
+static bool pmu_events_map__e_machine_matches(const struct pmu_events_map *map,
+\t\t\t\t\t      uint16_t e_machine)
+{
+\tif (e_machine == EM_386)
+\t\te_machine = EM_X86_64;
+\telse if (e_machine == EM_PPC)
+\t\te_machine = EM_PPC64;
+\treturn map->e_machine == e_machine;
+}
+
 static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
 {
 \tstatic struct {
@@ -1218,7 +1248,8 @@ static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
 \t\t\t\tbreak;
 \t\t\t}
 
-\t\t\tif (!strcmp_cpuid_str(map->cpuid, cpuid))
+\t\t\tif (pmu_events_map__e_machine_matches(map, EM_HOST) &&
+\t\t\t    !strcmp_cpuid_str(map->cpuid, cpuid))
 \t\t\t\tbreak;
 \t\t}
 \t\tfree(last_map_search.cpuid);
@@ -1503,6 +1534,8 @@ such as "arm/cortex-a34".''',
 #include <pmu-events/pmu-events.h>
 #include "util/header.h"
 #include "util/pmu.h"
+#include <dwarf-regs.h>
+#include <elf.h>
 #include <string.h>
 #include <stddef.h>
 
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 12/30] perf pmu-events: Pass e_machine to find_core_*_table()
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (10 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 11/30] perf pmu-events: Add e_machine to struct pmu_events_map Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:05   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 13/30] perf header: Pass e_machine to strcmp_cpuid_str() Ian Rogers
                   ` (17 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

find_core_events_table() and find_core_metrics_table() found tables by
an architecture name, such as from perf_env__arch(), that has to match
the pmu-events architecture directory name, for example "riscv" rather
than the "riscv64" from uname.

Instead find tables by the ELF machine of the CPUID's architecture,
using the e_machine recorded in struct pmu_events_map. The test and
common tables are found with EM_NONE.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/pmu-events/empty-pmu-events.c | 10 ++++++----
 tools/perf/pmu-events/jevents.py         | 10 ++++++----
 tools/perf/pmu-events/pmu-events.h       |  9 +++++++--
 tools/perf/tests/expand-cgroup.c         |  3 ++-
 tools/perf/tests/parse-metric.c          |  3 ++-
 tools/perf/tests/pmu-events.c            |  7 ++++---
 tools/perf/util/tool_pmu.c               |  3 ++-
 7 files changed, 29 insertions(+), 16 deletions(-)

diff --git a/tools/perf/pmu-events/empty-pmu-events.c b/tools/perf/pmu-events/empty-pmu-events.c
index 7428ffd4d70c..eba5446520c4 100644
--- a/tools/perf/pmu-events/empty-pmu-events.c
+++ b/tools/perf/pmu-events/empty-pmu-events.c
@@ -5927,23 +5927,25 @@ const struct pmu_metrics_table *pmu_metrics_table__default(void)
 	return NULL;
 }
 
-const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid)
+const struct pmu_events_table *find_core_events_table(uint16_t e_machine, const char *cpuid)
 {
 	for (const struct pmu_events_map *tables = &pmu_events_map[0];
 	     tables->arch;
 	     tables++) {
-		if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
+		if (pmu_events_map__e_machine_matches(tables, e_machine) &&
+		    !strcmp_cpuid_str(tables->cpuid, cpuid))
 			return &tables->event_table;
 	}
 	return NULL;
 }
 
-const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid)
+const struct pmu_metrics_table *find_core_metrics_table(uint16_t e_machine, const char *cpuid)
 {
 	for (const struct pmu_events_map *tables = &pmu_events_map[0];
 	     tables->arch;
 	     tables++) {
-		if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
+		if (pmu_events_map__e_machine_matches(tables, e_machine) &&
+		    !strcmp_cpuid_str(tables->cpuid, cpuid))
 			return &tables->metric_table;
 	}
 	return NULL;
diff --git a/tools/perf/pmu-events/jevents.py b/tools/perf/pmu-events/jevents.py
index db1b4749aec3..fc3de5fbda27 100755
--- a/tools/perf/pmu-events/jevents.py
+++ b/tools/perf/pmu-events/jevents.py
@@ -1345,23 +1345,25 @@ const struct pmu_metrics_table *pmu_metrics_table__default(void)
 \treturn NULL;
 }
 
-const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid)
+const struct pmu_events_table *find_core_events_table(uint16_t e_machine, const char *cpuid)
 {
 \tfor (const struct pmu_events_map *tables = &pmu_events_map[0];
 \t     tables->arch;
 \t     tables++) {
-\t\tif (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
+\t\tif (pmu_events_map__e_machine_matches(tables, e_machine) &&
+\t\t    !strcmp_cpuid_str(tables->cpuid, cpuid))
 \t\t\treturn &tables->event_table;
 \t}
 \treturn NULL;
 }
 
-const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid)
+const struct pmu_metrics_table *find_core_metrics_table(uint16_t e_machine, const char *cpuid)
 {
 \tfor (const struct pmu_events_map *tables = &pmu_events_map[0];
 \t     tables->arch;
 \t     tables++) {
-\t\tif (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid))
+\t\tif (pmu_events_map__e_machine_matches(tables, e_machine) &&
+\t\t    !strcmp_cpuid_str(tables->cpuid, cpuid))
 \t\t\treturn &tables->metric_table;
 \t}
 \treturn NULL;
diff --git a/tools/perf/pmu-events/pmu-events.h b/tools/perf/pmu-events/pmu-events.h
index cb55c9fbca43..c3f645c97ccd 100644
--- a/tools/perf/pmu-events/pmu-events.h
+++ b/tools/perf/pmu-events/pmu-events.h
@@ -4,6 +4,7 @@
 
 #include <stdbool.h>
 #include <stddef.h>
+#include <stdint.h>
 
 struct perf_pmu;
 
@@ -134,8 +135,12 @@ const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu)
 const struct pmu_events_table *perf_pmu__default_core_events_table(void);
 const struct pmu_metrics_table *pmu_metrics_table__find(void);
 const struct pmu_metrics_table *pmu_metrics_table__default(void);
-const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid);
-const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid);
+/*
+ * Find the core events or metrics table for a CPUID of the architecture with
+ * ELF machine @e_machine. The test and common tables use EM_NONE.
+ */
+const struct pmu_events_table *find_core_events_table(uint16_t e_machine, const char *cpuid);
+const struct pmu_metrics_table *find_core_metrics_table(uint16_t e_machine, const char *cpuid);
 int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data);
 int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data);
 
diff --git a/tools/perf/tests/expand-cgroup.c b/tools/perf/tests/expand-cgroup.c
index 04d62611766a..f5e092e8bb48 100644
--- a/tools/perf/tests/expand-cgroup.c
+++ b/tools/perf/tests/expand-cgroup.c
@@ -10,6 +10,7 @@
 #include "pfm.h"
 #include "target.h"
 #include <subcmd/parse-options.h>
+#include <elf.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -178,7 +179,7 @@ static int expand_metric_events(void)
 	evlist = evlist__new();
 	TEST_ASSERT_VAL("failed to get evlist", evlist);
 
-	pme_test = find_core_metrics_table("testarch", "testcpu");
+	pme_test = find_core_metrics_table(EM_NONE, "testcpu");
 	ret = metricgroup__parse_groups_test(evlist, pme_test, metric_str,
 					     /*cputype_filter=*/false);
 	if (ret < 0) {
diff --git a/tools/perf/tests/parse-metric.c b/tools/perf/tests/parse-metric.c
index 872d9a7aa72f..6f91e64a4123 100644
--- a/tools/perf/tests/parse-metric.c
+++ b/tools/perf/tests/parse-metric.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <linux/compiler.h>
+#include <elf.h>
 #include <errno.h>
 #include <string.h>
 #include <perf/cpumap.h>
@@ -91,7 +92,7 @@ static int __compute_metric(const char *name, struct value *vals,
 	perf_evlist__set_maps(evlist__core(evlist), cpus, NULL);
 
 	/* Parse the metric into metric_events list. */
-	pme_test = find_core_metrics_table("testarch", "testcpu");
+	pme_test = find_core_metrics_table(EM_NONE, "testcpu");
 	err = metricgroup__parse_groups_test(evlist, pme_test, name,
 					     /*cputype_filter=*/false);
 	if (err)
diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c
index cb0c2b05c1e8..8e82f7af2aa6 100644
--- a/tools/perf/tests/pmu-events.c
+++ b/tools/perf/tests/pmu-events.c
@@ -4,6 +4,7 @@
 #include "pmu.h"
 #include "pmus.h"
 #include "tests.h"
+#include <elf.h>
 #include <errno.h>
 #include <stdio.h>
 #include <linux/kernel.h>
@@ -449,7 +450,7 @@ static int test__pmu_event_table(struct test_suite *test __maybe_unused,
 {
 	const struct pmu_events_table *sys_event_table =
 		find_sys_events_table("pmu_events__test_soc_sys");
-	const struct pmu_events_table *table = find_core_events_table("testarch", "testcpu");
+	const struct pmu_events_table *table = find_core_events_table(EM_NONE, "testcpu");
 	int map_events = 0, expected_events, err;
 
 	/* ignore 3x sentinels */
@@ -504,7 +505,7 @@ static int __test_core_pmu_event_aliases(const char *pmu_name, int *count)
 	struct perf_pmu_test_event const **test_event_table;
 	struct perf_pmu *pmu;
 	int res = 0;
-	const struct pmu_events_table *table = find_core_events_table("testarch", "testcpu");
+	const struct pmu_events_table *table = find_core_events_table(EM_NONE, "testcpu");
 
 	if (!table)
 		return -1;
@@ -559,7 +560,7 @@ static int __test_uncore_pmu_event_aliases(struct perf_pmu_test_pmu *test_pmu)
 	const struct pmu_events_table *events_table;
 	int res = 0;
 
-	events_table = find_core_events_table("testarch", "testcpu");
+	events_table = find_core_events_table(EM_NONE, "testcpu");
 	if (!events_table)
 		return -1;
 
diff --git a/tools/perf/util/tool_pmu.c b/tools/perf/util/tool_pmu.c
index 6054d5d5aece..d09361999b82 100644
--- a/tools/perf/util/tool_pmu.c
+++ b/tools/perf/util/tool_pmu.c
@@ -14,6 +14,7 @@
 #include <api/io.h>
 #include <internal/threadmap.h>
 #include <perf/threadmap.h>
+#include <elf.h>
 #include <fcntl.h>
 #include <strings.h>
 
@@ -718,6 +719,6 @@ struct perf_pmu *tool_pmu__new(void)
 		perf_pmu__delete(tool);
 		return NULL;
 	}
-	tool->events_table = find_core_events_table("common", "common");
+	tool->events_table = find_core_events_table(EM_NONE, "common");
 	return tool;
 }
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 13/30] perf header: Pass e_machine to strcmp_cpuid_str()
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (11 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 12/30] perf pmu-events: Pass e_machine to find_core_*_table() Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:01   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 14/30] perf header: Support CPUID matching across all architectures Ian Rogers
                   ` (16 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

The format of a CPUID depends on its architecture, but
strcmp_cpuid_str() is only given the two CPUID strings and its
architecture specific implementations assume the CPUIDs are for the
architecture perf was built for.

Pass the ELF machine of the CPUIDs' architecture to strcmp_cpuid_str()
so that CPUIDs can be compared according to their architecture rather
than the host's. The pmu-events tables pass their e_machine, comparisons
with the host's CPUID pass EM_HOST and the arm64 CPUID matching test
passes EM_AARCH64. The architecture specific implementations don't yet
use the argument.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/arch/arm64/tests/cpuid-match.c | 17 +++++++++--------
 tools/perf/arch/arm64/util/header.c       |  2 +-
 tools/perf/arch/x86/util/header.c         |  2 +-
 tools/perf/arch/x86/util/pmu.c            | 10 ++++++----
 tools/perf/pmu-events/empty-pmu-events.c  |  6 +++---
 tools/perf/pmu-events/jevents.py          |  6 +++---
 tools/perf/util/expr.c                    |  3 ++-
 tools/perf/util/header.c                  |  3 ++-
 tools/perf/util/header.h                  |  6 +++++-
 9 files changed, 32 insertions(+), 23 deletions(-)

diff --git a/tools/perf/arch/arm64/tests/cpuid-match.c b/tools/perf/arch/arm64/tests/cpuid-match.c
index e8e3947cca18..b073ac33ea9b 100644
--- a/tools/perf/arch/arm64/tests/cpuid-match.c
+++ b/tools/perf/arch/arm64/tests/cpuid-match.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <linux/compiler.h>
+#include <elf.h>
 
 #include "arch-tests.h"
 #include "tests/tests.h"
@@ -9,28 +10,28 @@ int test__cpuid_match(struct test_suite *test __maybe_unused,
 			     int subtest __maybe_unused)
 {
 	/* midr with no leading zeros matches */
-	if (strcmp_cpuid_str("0x410fd0c0", "0x00000000410fd0c0"))
+	if (strcmp_cpuid_str(EM_AARCH64, "0x410fd0c0", "0x00000000410fd0c0"))
 		return -1;
 	/* Upper case matches */
-	if (strcmp_cpuid_str("0x410fd0c0", "0x00000000410FD0C0"))
+	if (strcmp_cpuid_str(EM_AARCH64, "0x410fd0c0", "0x00000000410FD0C0"))
 		return -1;
 	/* r0p0 = r0p0 matches */
-	if (strcmp_cpuid_str("0x00000000410fd480", "0x00000000410fd480"))
+	if (strcmp_cpuid_str(EM_AARCH64, "0x00000000410fd480", "0x00000000410fd480"))
 		return -1;
 	/* r0p1 > r0p0 matches */
-	if (strcmp_cpuid_str("0x00000000410fd480", "0x00000000410fd481"))
+	if (strcmp_cpuid_str(EM_AARCH64, "0x00000000410fd480", "0x00000000410fd481"))
 		return -1;
 	/* r1p0 > r0p0 matches*/
-	if (strcmp_cpuid_str("0x00000000410fd480", "0x00000000411fd480"))
+	if (strcmp_cpuid_str(EM_AARCH64, "0x00000000410fd480", "0x00000000411fd480"))
 		return -1;
 	/* r0p0 < r0p1 doesn't match */
-	if (!strcmp_cpuid_str("0x00000000410fd481", "0x00000000410fd480"))
+	if (!strcmp_cpuid_str(EM_AARCH64, "0x00000000410fd481", "0x00000000410fd480"))
 		return -1;
 	/* r0p0 < r1p0 doesn't match */
-	if (!strcmp_cpuid_str("0x00000000411fd480", "0x00000000410fd480"))
+	if (!strcmp_cpuid_str(EM_AARCH64, "0x00000000411fd480", "0x00000000410fd480"))
 		return -1;
 	/* Different CPU doesn't match */
-	if (!strcmp_cpuid_str("0x00000000410fd4c0", "0x00000000430f0af0"))
+	if (!strcmp_cpuid_str(EM_AARCH64, "0x00000000410fd4c0", "0x00000000430f0af0"))
 		return -1;
 
 	return 0;
diff --git a/tools/perf/arch/arm64/util/header.c b/tools/perf/arch/arm64/util/header.c
index 95e71c4f6c78..ee28add836b5 100644
--- a/tools/perf/arch/arm64/util/header.c
+++ b/tools/perf/arch/arm64/util/header.c
@@ -87,7 +87,7 @@ char *get_cpuid_str(struct perf_cpu cpu)
  *
  * Return 1 if the CPU type is different or the version of idstr is lower.
  */
-int strcmp_cpuid_str(const char *mapcpuid, const char *idstr)
+int strcmp_cpuid_str(uint16_t e_machine __maybe_unused, const char *mapcpuid, const char *idstr)
 {
 	u64 map_id = strtoull(mapcpuid, NULL, 16);
 	char map_id_variant = FIELD_GET(MIDR_VARIANT_MASK, map_id);
diff --git a/tools/perf/arch/x86/util/header.c b/tools/perf/arch/x86/util/header.c
index 412977f8aa83..b65856f3b846 100644
--- a/tools/perf/arch/x86/util/header.c
+++ b/tools/perf/arch/x86/util/header.c
@@ -91,7 +91,7 @@ static bool is_full_cpuid(const char *id)
 	return false;
 }
 
-int strcmp_cpuid_str(const char *mapcpuid, const char *id)
+int strcmp_cpuid_str(uint16_t e_machine __maybe_unused, const char *mapcpuid, const char *id)
 {
 	regex_t re;
 	regmatch_t pmatch[1];
diff --git a/tools/perf/arch/x86/util/pmu.c b/tools/perf/arch/x86/util/pmu.c
index fd4491fda117..37269154e5e7 100644
--- a/tools/perf/arch/x86/util/pmu.c
+++ b/tools/perf/arch/x86/util/pmu.c
@@ -22,6 +22,7 @@
 #include "util/debug.h"
 #include "util/env.h"
 #include "util/header.h"
+#include "dwarf-regs.h"
 
 #define GENUINE_INTEL_SPR "GenuineIntel-6-8F"
 #define GENUINE_INTEL_EMR "GenuineIntel-6-CF"
@@ -41,7 +42,8 @@ static void init_snc_support(void)
 	char *cpuid = get_cpuid_str((struct perf_cpu){0});
 
 	for (size_t i = 0; i < ARRAY_SIZE(supported_cpuids); i++) {
-		cached_snc_supported = cpuid && strcmp_cpuid_str(supported_cpuids[i], cpuid) == 0;
+		cached_snc_supported = cpuid &&
+			strcmp_cpuid_str(EM_HOST, supported_cpuids[i], cpuid) == 0;
 		if (cached_snc_supported)
 			break;
 	}
@@ -173,11 +175,11 @@ static void init_snc_map(void)
 	case 2:
 		cpuid = get_cpuid_str((struct perf_cpu){ 0 });
 		if (cpuid) {
-			if (strcmp_cpuid_str(GENUINE_INTEL_SPR, cpuid) == 0 ||
-			    strcmp_cpuid_str(GENUINE_INTEL_EMR, cpuid) == 0) {
+			if (strcmp_cpuid_str(EM_HOST, GENUINE_INTEL_SPR, cpuid) == 0 ||
+			    strcmp_cpuid_str(EM_HOST, GENUINE_INTEL_EMR, cpuid) == 0) {
 				cached_imc_snc_map = spr_emr_snc2_map;
 				cached_imc_snc_map_len = ARRAY_SIZE(spr_emr_snc2_map);
-			} else if (strcmp_cpuid_str(GENUINE_INTEL_GNR, cpuid) == 0) {
+			} else if (strcmp_cpuid_str(EM_HOST, GENUINE_INTEL_GNR, cpuid) == 0) {
 				cached_imc_snc_map = gnr_snc2_map;
 				cached_imc_snc_map_len = ARRAY_SIZE(gnr_snc2_map);
 			}
diff --git a/tools/perf/pmu-events/empty-pmu-events.c b/tools/perf/pmu-events/empty-pmu-events.c
index eba5446520c4..872c6ca6e817 100644
--- a/tools/perf/pmu-events/empty-pmu-events.c
+++ b/tools/perf/pmu-events/empty-pmu-events.c
@@ -5831,7 +5831,7 @@ static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
 			}
 
 			if (pmu_events_map__e_machine_matches(map, EM_HOST) &&
-			    !strcmp_cpuid_str(map->cpuid, cpuid))
+			    !strcmp_cpuid_str(map->e_machine, map->cpuid, cpuid))
 				break;
 		}
 		free(last_map_search.cpuid);
@@ -5933,7 +5933,7 @@ const struct pmu_events_table *find_core_events_table(uint16_t e_machine, const
 	     tables->arch;
 	     tables++) {
 		if (pmu_events_map__e_machine_matches(tables, e_machine) &&
-		    !strcmp_cpuid_str(tables->cpuid, cpuid))
+		    !strcmp_cpuid_str(tables->e_machine, tables->cpuid, cpuid))
 			return &tables->event_table;
 	}
 	return NULL;
@@ -5945,7 +5945,7 @@ const struct pmu_metrics_table *find_core_metrics_table(uint16_t e_machine, cons
 	     tables->arch;
 	     tables++) {
 		if (pmu_events_map__e_machine_matches(tables, e_machine) &&
-		    !strcmp_cpuid_str(tables->cpuid, cpuid))
+		    !strcmp_cpuid_str(tables->e_machine, tables->cpuid, cpuid))
 			return &tables->metric_table;
 	}
 	return NULL;
diff --git a/tools/perf/pmu-events/jevents.py b/tools/perf/pmu-events/jevents.py
index fc3de5fbda27..802ae2ad4405 100755
--- a/tools/perf/pmu-events/jevents.py
+++ b/tools/perf/pmu-events/jevents.py
@@ -1249,7 +1249,7 @@ static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
 \t\t\t}
 
 \t\t\tif (pmu_events_map__e_machine_matches(map, EM_HOST) &&
-\t\t\t    !strcmp_cpuid_str(map->cpuid, cpuid))
+\t\t\t    !strcmp_cpuid_str(map->e_machine, map->cpuid, cpuid))
 \t\t\t\tbreak;
 \t\t}
 \t\tfree(last_map_search.cpuid);
@@ -1351,7 +1351,7 @@ const struct pmu_events_table *find_core_events_table(uint16_t e_machine, const
 \t     tables->arch;
 \t     tables++) {
 \t\tif (pmu_events_map__e_machine_matches(tables, e_machine) &&
-\t\t    !strcmp_cpuid_str(tables->cpuid, cpuid))
+\t\t    !strcmp_cpuid_str(tables->e_machine, tables->cpuid, cpuid))
 \t\t\treturn &tables->event_table;
 \t}
 \treturn NULL;
@@ -1363,7 +1363,7 @@ const struct pmu_metrics_table *find_core_metrics_table(uint16_t e_machine, cons
 \t     tables->arch;
 \t     tables++) {
 \t\tif (pmu_events_map__e_machine_matches(tables, e_machine) &&
-\t\t    !strcmp_cpuid_str(tables->cpuid, cpuid))
+\t\t    !strcmp_cpuid_str(tables->e_machine, tables->cpuid, cpuid))
 \t\t\treturn &tables->metric_table;
 \t}
 \treturn NULL;
diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index f1164c4666b1..09ced31ef59e 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -14,6 +14,7 @@
 #include <util/expr-flex.h>
 #include "util/hashmap.h"
 #include "util/header.h"
+#include "dwarf-regs.h"
 #include "util/pmu.h"
 #include <perf/cpumap.h>
 #include <linux/err.h>
@@ -485,7 +486,7 @@ double expr__strcmp_cpuid_str(const struct expr_parse_ctx *ctx __maybe_unused,
 	if (!cpuid)
 		return NAN;
 
-	ret = !strcmp_cpuid_str(test_id, cpuid);
+	ret = !strcmp_cpuid_str(EM_HOST, test_id, cpuid);
 
 	free(cpuid);
 	return ret;
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
index 2c686095fe67..38525b17f26d 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -964,7 +964,8 @@ char *get_cpuid_allow_env_override(struct perf_cpu cpu)
  * cpuid string generated on this platform.
  * Otherwise return non-zero.
  */
-int __weak strcmp_cpuid_str(const char *mapcpuid, const char *cpuid)
+int __weak strcmp_cpuid_str(uint16_t e_machine __maybe_unused, const char *mapcpuid,
+			    const char *cpuid)
 {
 	regex_t re;
 	regmatch_t pmatch[1];
diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h
index 765037762758..d30fb30a0c92 100644
--- a/tools/perf/util/header.h
+++ b/tools/perf/util/header.h
@@ -219,7 +219,11 @@ char *get_cpuid_str(struct perf_cpu cpu);
 
 char *get_cpuid_allow_env_override(struct perf_cpu cpu);
 
-int strcmp_cpuid_str(const char *s1, const char *s2);
+/*
+ * Compare a CPUID from a pmu-events mapfile.csv, @mapcpuid, with @cpuid for
+ * the architecture with ELF machine @e_machine. Returns 0 on a match.
+ */
+int strcmp_cpuid_str(uint16_t e_machine, const char *mapcpuid, const char *cpuid);
 
 struct cpu_domain_map **build_cpu_domain_map(u32 *schedstat_version, u32 *max_sched_domains,
 					     u32 nr);
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 14/30] perf header: Support CPUID matching across all architectures
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (12 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 13/30] perf header: Pass e_machine to strcmp_cpuid_str() Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:05   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 15/30] perf test: Make the CPUID matching test architecture independent Ian Rogers
                   ` (15 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

strcmp_cpuid_str() had architecture specific overrides of a __weak
regular expression matching implementation in arch/x86/util/header.c
and arch/arm64/util/header.c, so a CPUID could only be compared using
the rules of the architecture perf was built for. In addition, the
HEADER_CPUID strings written by get_cpuid() on x86 ("vendor,family,
model,step" in decimal) and PowerPC ("pvr_ver,pvr_rev" in decimal)
differ from the get_cpuid_str() formats ("vendor-family-model-stepping"
in hex and "0x%.8lx") matched against the pmu-events mapfile.csv
tables. As a result, finding the core events or metrics table for the
CPUID of a perf_env, such as from a perf.data file recorded on a
different architecture, failed.

Move the architecture specific comparisons into util/header.c and
select them using the ELF machine of the CPUIDs: EM_NONE for the test
and common tables compares exactly, arm64 compares MIDR part, variant
and revision fields, x86 matches with an optional stepping and other
architectures match a regular expression. Convert x86 and PowerPC
HEADER_CPUID strings before matching.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/arch/arm64/util/header.c |  48 ---------
 tools/perf/arch/x86/util/header.c   |  64 -----------
 tools/perf/util/header.c            | 161 +++++++++++++++++++++++++++-
 3 files changed, 156 insertions(+), 117 deletions(-)

diff --git a/tools/perf/arch/arm64/util/header.c b/tools/perf/arch/arm64/util/header.c
index ee28add836b5..181836613087 100644
--- a/tools/perf/arch/arm64/util/header.c
+++ b/tools/perf/arch/arm64/util/header.c
@@ -1,5 +1,3 @@
-#include <linux/bits.h>
-#include <linux/bitfield.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <perf/cpumap.h>
@@ -10,8 +8,6 @@
 
 #define MIDR "/regs/identification/midr_el1"
 #define MIDR_SIZE 19
-#define MIDR_REVISION_MASK      GENMASK(3, 0)
-#define MIDR_VARIANT_MASK	GENMASK(23, 20)
 
 static int _get_cpuid(char *buf, size_t sz, struct perf_cpu cpu)
 {
@@ -79,47 +75,3 @@ char *get_cpuid_str(struct perf_cpu cpu)
 
 	return buf;
 }
-
-/*
- * Return 0 if idstr is a higher or equal to version of the same part as
- * mapcpuid. Therefore, if mapcpuid has 0 for revision and variant then any
- * version of idstr will match as long as it's the same CPU type.
- *
- * Return 1 if the CPU type is different or the version of idstr is lower.
- */
-int strcmp_cpuid_str(uint16_t e_machine __maybe_unused, const char *mapcpuid, const char *idstr)
-{
-	u64 map_id = strtoull(mapcpuid, NULL, 16);
-	char map_id_variant = FIELD_GET(MIDR_VARIANT_MASK, map_id);
-	char map_id_revision = FIELD_GET(MIDR_REVISION_MASK, map_id);
-	u64 id = strtoull(idstr, NULL, 16);
-	char id_variant = FIELD_GET(MIDR_VARIANT_MASK, id);
-	char id_revision = FIELD_GET(MIDR_REVISION_MASK, id);
-	u64 id_fields = ~(MIDR_VARIANT_MASK | MIDR_REVISION_MASK);
-
-	/* Compare without version first */
-	if ((map_id & id_fields) != (id & id_fields))
-		return 1;
-
-	/*
-	 * ID matches, now compare version.
-	 *
-	 * Arm revisions (like r0p0) are compared here like two digit semver
-	 * values eg. 1.3 < 2.0 < 2.1 < 2.2.
-	 *
-	 *  r = high value = 'Variant' field in MIDR
-	 *  p = low value  = 'Revision' field in MIDR
-	 *
-	 */
-	if (id_variant > map_id_variant)
-		return 0;
-
-	if (id_variant == map_id_variant && id_revision >= map_id_revision)
-		return 0;
-
-	/*
-	 * variant is less than mapfile variant or variants are the same but
-	 * the revision doesn't match. Return no match.
-	 */
-	return 1;
-}
diff --git a/tools/perf/arch/x86/util/header.c b/tools/perf/arch/x86/util/header.c
index b65856f3b846..5203e5cd151c 100644
--- a/tools/perf/arch/x86/util/header.c
+++ b/tools/perf/arch/x86/util/header.c
@@ -5,7 +5,6 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <regex.h>
 
 #include "../../../util/debug.h"
 #include "../../../util/header.h"
@@ -73,66 +72,3 @@ char *get_cpuid_str(struct perf_cpu cpu __maybe_unused)
 	}
 	return buf;
 }
-
-/* Full CPUID format for x86 is vendor-family-model-stepping */
-static bool is_full_cpuid(const char *id)
-{
-	const char *tmp = id;
-	int count = 0;
-
-	while ((tmp = strchr(tmp, '-')) != NULL) {
-		count++;
-		tmp++;
-	}
-
-	if (count == 3)
-		return true;
-
-	return false;
-}
-
-int strcmp_cpuid_str(uint16_t e_machine __maybe_unused, const char *mapcpuid, const char *id)
-{
-	regex_t re;
-	regmatch_t pmatch[1];
-	int match;
-	bool full_mapcpuid = is_full_cpuid(mapcpuid);
-	bool full_cpuid = is_full_cpuid(id);
-
-	/*
-	 * Full CPUID format is required to identify a platform.
-	 * Error out if the cpuid string is incomplete.
-	 */
-	if (full_mapcpuid && !full_cpuid) {
-		pr_info("Invalid CPUID %s. Full CPUID is required, "
-			"vendor-family-model-stepping\n", id);
-		return 1;
-	}
-
-	if (regcomp(&re, mapcpuid, REG_EXTENDED) != 0) {
-		/* Warn unable to generate match particular string. */
-		pr_info("Invalid regular expression %s\n", mapcpuid);
-		return 1;
-	}
-
-	match = !regexec(&re, id, 1, pmatch, 0);
-	regfree(&re);
-	if (match) {
-		size_t match_len = (pmatch[0].rm_eo - pmatch[0].rm_so);
-		size_t cpuid_len;
-
-		/* If the full CPUID format isn't required,
-		 * ignoring the stepping.
-		 */
-		if (!full_mapcpuid && full_cpuid)
-			cpuid_len = strrchr(id, '-') - id;
-		else
-			cpuid_len = strlen(id);
-
-		/* Verify the entire string matched. */
-		if (match_len == cpuid_len)
-			return 0;
-	}
-
-	return 1;
-}
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
index 38525b17f26d..a442f2a904a9 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -11,7 +11,10 @@
 
 #include <byteswap.h>
 #include <dirent.h>
+#include <elf.h>
+#include <linux/bitfield.h>
 #include <linux/bitops.h>
+#include <linux/bits.h>
 #include <linux/compiler.h>
 #include <linux/ctype.h>
 #include <linux/kernel.h>
@@ -960,12 +963,70 @@ char *get_cpuid_allow_env_override(struct perf_cpu cpu)
 	return cpuid;
 }
 
-/* Return zero when the cpuid from the mapfile.csv matches the
- * cpuid string generated on this platform.
- * Otherwise return non-zero.
+#define MIDR_REVISION_MASK	GENMASK(3, 0)
+#define MIDR_VARIANT_MASK	GENMASK(23, 20)
+
+/* Is @id a full x86 vendor-family-model-stepping CPUID, with 3 hyphens? */
+static bool is_full_x86_cpuid(const char *id)
+{
+	const char *tmp = id;
+	int count = 0;
+
+	while ((tmp = strchr(tmp, '-')) != NULL) {
+		count++;
+		tmp++;
+	}
+
+	return count == 3;
+}
+
+/*
+ * Compare arm64 MIDR values. Return 0 if @idstr is the same CPU type as
+ * @mapcpuid and its version, the Variant[23:20] and Revision[3:0] fields, is
+ * greater than or equal to that of @mapcpuid. Therefore, if @mapcpuid has 0 for
+ * the revision and variant then any version of @idstr will match as long as
+ * it's the same CPU type.
+ *
+ * Return 1 if the CPU type is different or the version of @idstr is lower.
  */
-int __weak strcmp_cpuid_str(uint16_t e_machine __maybe_unused, const char *mapcpuid,
-			    const char *cpuid)
+static int strcmp_arm64_cpuid_str(const char *mapcpuid, const char *idstr)
+{
+	u64 map_id = strtoull(mapcpuid, NULL, 16);
+	char map_id_variant = FIELD_GET(MIDR_VARIANT_MASK, map_id);
+	char map_id_revision = FIELD_GET(MIDR_REVISION_MASK, map_id);
+	u64 id = strtoull(idstr, NULL, 16);
+	char id_variant = FIELD_GET(MIDR_VARIANT_MASK, id);
+	char id_revision = FIELD_GET(MIDR_REVISION_MASK, id);
+	u64 id_fields = ~(MIDR_VARIANT_MASK | MIDR_REVISION_MASK);
+
+	/* Compare without version first. */
+	if ((map_id & id_fields) != (id & id_fields))
+		return 1;
+
+	/*
+	 * ID matches, now compare version.
+	 *
+	 * Arm revisions (like r0p0) are compared here like two digit semver
+	 * values eg. 1.3 < 2.0 < 2.1 < 2.2.
+	 *
+	 *  r = high value = 'Variant' field in MIDR
+	 *  p = low value  = 'Revision' field in MIDR
+	 */
+	if (id_variant > map_id_variant)
+		return 0;
+
+	if (id_variant == map_id_variant && id_revision >= map_id_revision)
+		return 0;
+
+	/*
+	 * Variant is less than mapfile variant or variants are the same but
+	 * the revision doesn't match. Return no match.
+	 */
+	return 1;
+}
+
+/* Return 0 if all of @cpuid matches the extended regular expression @mapcpuid. */
+static int strcmp_regex_cpuid_str(const char *mapcpuid, const char *cpuid)
 {
 	regex_t re;
 	regmatch_t pmatch[1];
@@ -989,6 +1050,96 @@ int __weak strcmp_cpuid_str(uint16_t e_machine __maybe_unused, const char *mapcp
 	return 1;
 }
 
+/*
+ * Compare x86 CPUIDs of the form vendor-family-model[-stepping]. A full
+ * @mapcpuid is stepping specific and requires a full @cpuid, otherwise the
+ * stepping of a full @cpuid is ignored.
+ */
+static int strcmp_x86_cpuid_str(const char *mapcpuid, const char *cpuid)
+{
+	unsigned int family, model, step;
+	char norm_buf[128], buf[128];
+	char vendor[32], extra;
+	bool full_mapcpuid, full_cpuid;
+
+	/* x86 get_cpuid() writes "vendor,family,model,step" in decimal. */
+	if (sscanf(cpuid, "%31[^,],%u,%u,%u%c", vendor, &family, &model, &step, &extra) == 4) {
+		snprintf(norm_buf, sizeof(norm_buf), "%s-%u-%X-%X", vendor, family, model, step);
+		cpuid = norm_buf;
+	}
+
+	full_mapcpuid = is_full_x86_cpuid(mapcpuid);
+	full_cpuid = is_full_x86_cpuid(cpuid);
+
+	/*
+	 * Full CPUID format is required to identify a stepping specific
+	 * platform. Error out if the cpuid string is incomplete.
+	 */
+	if (full_mapcpuid && !full_cpuid) {
+		pr_info("Invalid CPUID %s. Full CPUID is required, vendor-family-model-stepping\n",
+			cpuid);
+		return 1;
+	}
+
+	/* If the full CPUID format isn't required, ignore the stepping. */
+	if (!full_mapcpuid && full_cpuid) {
+		int len = strrchr(cpuid, '-') - cpuid;
+
+		snprintf(buf, sizeof(buf), "%.*s", len, cpuid);
+		cpuid = buf;
+	}
+	return strcmp_regex_cpuid_str(mapcpuid, cpuid);
+}
+
+/* Compare PowerPC PVR CPUIDs, matching @cpuid against the regex @mapcpuid. */
+static int strcmp_powerpc_cpuid_str(const char *mapcpuid, const char *cpuid)
+{
+	unsigned long pvr_ver, pvr_rev;
+	char norm_buf[32], extra;
+
+	/* PowerPC get_cpuid() writes "pvr_ver,pvr_rev" in decimal. */
+	if (sscanf(cpuid, "%lu,%lu%c", &pvr_ver, &pvr_rev, &extra) == 2) {
+		unsigned long pvr = ((pvr_ver & 0xffff) << 16) | (pvr_rev & 0xffff);
+
+		snprintf(norm_buf, sizeof(norm_buf), "0x%.8lx", pvr);
+		cpuid = norm_buf;
+	}
+	return strcmp_regex_cpuid_str(mapcpuid, cpuid);
+}
+
+/**
+ * strcmp_cpuid_str - Compare a pmu-events mapfile.csv CPUID with a CPUID.
+ * @e_machine: The ELF machine of the CPUIDs' architecture.
+ * @mapcpuid: The CPUID from the mapfile.csv, a regular expression for most
+ *            architectures.
+ * @cpuid: The CPUID from get_cpuid_str() or HEADER_CPUID.
+ *
+ * The test and common tables use EM_NONE and are compared exactly, arm64
+ * compares MIDR values and other architectures match @cpuid against the
+ * regular expression @mapcpuid. HEADER_CPUID values written by get_cpuid() on
+ * x86 and PowerPC differ from the get_cpuid_str() format of the mapfile.csv
+ * and are converted before matching.
+ *
+ * Return: 0 if the CPUIDs match, otherwise 1.
+ */
+int strcmp_cpuid_str(uint16_t e_machine, const char *mapcpuid, const char *cpuid)
+{
+	switch (e_machine) {
+	case EM_NONE:
+		return strcmp(mapcpuid, cpuid) ? 1 : 0;
+	case EM_AARCH64:
+		return strcmp_arm64_cpuid_str(mapcpuid, cpuid);
+	case EM_386:
+	case EM_X86_64:
+		return strcmp_x86_cpuid_str(mapcpuid, cpuid);
+	case EM_PPC:
+	case EM_PPC64:
+		return strcmp_powerpc_cpuid_str(mapcpuid, cpuid);
+	default:
+		return strcmp_regex_cpuid_str(mapcpuid, cpuid);
+	}
+}
+
 /*
  * default get_cpuid(): nothing gets recorded
  * actual implementation must be in arch/$(SRCARCH)/util/header.c
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 15/30] perf test: Make the CPUID matching test architecture independent
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (13 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 14/30] perf header: Support CPUID matching across all architectures Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:02   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 16/30] perf test: Add CPUID matching tests for other architectures Ian Rogers
                   ` (14 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

The arm64 CPUID matching test called strcmp_cpuid_str(), whose arm64
MIDR comparison was only built for arm64. Now that strcmp_cpuid_str()
is given the ELF machine of the CPUIDs and all of its comparisons are
in util/header.c, the test can run on every architecture.

Move the test from arch/arm64/tests to tests, as a "CPUID matching"
suite with an "arm64 MIDR" test case.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/arch/arm64/include/arch-tests.h      |  1 -
 tools/perf/arch/arm64/tests/Build               |  1 -
 tools/perf/arch/arm64/tests/arch-tests.c        |  4 ----
 tools/perf/tests/Build                          |  1 +
 tools/perf/tests/builtin-test.c                 |  1 +
 tools/perf/{arch/arm64 => }/tests/cpuid-match.c | 17 +++++++++++++----
 tools/perf/tests/tests.h                        |  1 +
 7 files changed, 16 insertions(+), 10 deletions(-)
 rename tools/perf/{arch/arm64 => }/tests/cpuid-match.c (75%)

diff --git a/tools/perf/arch/arm64/include/arch-tests.h b/tools/perf/arch/arm64/include/arch-tests.h
index 474d7cf5afbd..2b3834a5ddbb 100644
--- a/tools/perf/arch/arm64/include/arch-tests.h
+++ b/tools/perf/arch/arm64/include/arch-tests.h
@@ -4,7 +4,6 @@
 
 struct test_suite;
 
-int test__cpuid_match(struct test_suite *test, int subtest);
 extern struct test_suite *arch_tests[];
 
 #endif
diff --git a/tools/perf/arch/arm64/tests/Build b/tools/perf/arch/arm64/tests/Build
index d44c9de92d42..bd15c6bbc1b2 100644
--- a/tools/perf/arch/arm64/tests/Build
+++ b/tools/perf/arch/arm64/tests/Build
@@ -2,4 +2,3 @@ perf-test-y += regs_load.o
 perf-test-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o
 
 perf-test-y += arch-tests.o
-perf-test-y += cpuid-match.o
diff --git a/tools/perf/arch/arm64/tests/arch-tests.c b/tools/perf/arch/arm64/tests/arch-tests.c
index 74932e72c727..ad16b4f8f63e 100644
--- a/tools/perf/arch/arm64/tests/arch-tests.c
+++ b/tools/perf/arch/arm64/tests/arch-tests.c
@@ -3,13 +3,9 @@
 #include "tests/tests.h"
 #include "arch-tests.h"
 
-
-DEFINE_SUITE("arm64 CPUID matching", cpuid_match);
-
 struct test_suite *arch_tests[] = {
 #ifdef HAVE_DWARF_UNWIND_SUPPORT
 	&suite__dwarf_unwind,
 #endif
-	&suite__cpuid_match,
 	NULL,
 };
diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build
index 5eebb2ec5dde..404da9804f48 100644
--- a/tools/perf/tests/Build
+++ b/tools/perf/tests/Build
@@ -45,6 +45,7 @@ perf-test-y += stat.o
 perf-test-y += event_update.o
 perf-test-y += event-times.o
 perf-test-y += expr.o
+perf-test-y += cpuid-match.o
 perf-test-y += backward-ring-buffer.o
 perf-test-y += sdt.o
 perf-test-y += is_printable_array.o
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index 327483f96401..e8ce94321239 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -192,6 +192,7 @@ static struct test_suite *generic_tests[] = {
 	&suite__util,
 	&suite__subcmd_help,
 	&suite__kallsyms_split,
+	&suite__cpuid_match,
 	NULL,
 };
 
diff --git a/tools/perf/arch/arm64/tests/cpuid-match.c b/tools/perf/tests/cpuid-match.c
similarity index 75%
rename from tools/perf/arch/arm64/tests/cpuid-match.c
rename to tools/perf/tests/cpuid-match.c
index b073ac33ea9b..e3d4a349f393 100644
--- a/tools/perf/arch/arm64/tests/cpuid-match.c
+++ b/tools/perf/tests/cpuid-match.c
@@ -2,12 +2,11 @@
 #include <linux/compiler.h>
 #include <elf.h>
 
-#include "arch-tests.h"
-#include "tests/tests.h"
+#include "tests.h"
 #include "util/header.h"
 
-int test__cpuid_match(struct test_suite *test __maybe_unused,
-			     int subtest __maybe_unused)
+static int test__cpuid_match_arm64(struct test_suite *test __maybe_unused,
+				   int subtest __maybe_unused)
 {
 	/* midr with no leading zeros matches */
 	if (strcmp_cpuid_str(EM_AARCH64, "0x410fd0c0", "0x00000000410fd0c0"))
@@ -36,3 +35,13 @@ int test__cpuid_match(struct test_suite *test __maybe_unused,
 
 	return 0;
 }
+
+static struct test_case tests__cpuid_match[] = {
+	TEST_CASE("arm64 MIDR", cpuid_match_arm64),
+	{ .name = NULL, }
+};
+
+struct test_suite suite__cpuid_match = {
+	.desc = "CPUID matching",
+	.test_cases = tests__cpuid_match,
+};
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
index af822885890d..2306ffb20b57 100644
--- a/tools/perf/tests/tests.h
+++ b/tools/perf/tests/tests.h
@@ -203,6 +203,7 @@ DECLARE_SUITE(util);
 DECLARE_SUITE(uncore_event_sorting);
 DECLARE_SUITE(subcmd_help);
 DECLARE_SUITE(kallsyms_split);
+DECLARE_SUITE(cpuid_match);
 
 /*
  * PowerPC and S390 do not support creation of instruction breakpoints using the
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 16/30] perf test: Add CPUID matching tests for other architectures
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (14 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 15/30] perf test: Make the CPUID matching test architecture independent Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:03   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 17/30] perf dwarf-regs: Add e_machine__same_arch() Ian Rogers
                   ` (13 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Test strcmp_cpuid_str() for the ELF machines other than arm64: exact
matching of the test and common tables, x86 stepping specific and
stepping optional matching, PowerPC PVR matching, conversion of x86
and PowerPC HEADER_CPUID strings, and regular expression matching for
RISC-V and s390 CPUIDs, where RISC-V's hyphens don't imply an x86
stepping.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/tests/cpuid-match.c | 79 ++++++++++++++++++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/tools/perf/tests/cpuid-match.c b/tools/perf/tests/cpuid-match.c
index e3d4a349f393..826380241a0f 100644
--- a/tools/perf/tests/cpuid-match.c
+++ b/tools/perf/tests/cpuid-match.c
@@ -36,8 +36,87 @@ static int test__cpuid_match_arm64(struct test_suite *test __maybe_unused,
 	return 0;
 }
 
+static int test__cpuid_match_exact(struct test_suite *test __maybe_unused,
+				   int subtest __maybe_unused)
+{
+	/* The test and common tables are compared exactly. */
+	TEST_ASSERT_VAL("same", !strcmp_cpuid_str(EM_NONE, "testcpu", "testcpu"));
+	TEST_ASSERT_VAL("different", strcmp_cpuid_str(EM_NONE, "common", "testcpu"));
+	TEST_ASSERT_VAL("no regex", strcmp_cpuid_str(EM_NONE, "test.*", "testcpu"));
+	return 0;
+}
+
+static int test__cpuid_match_x86(struct test_suite *test __maybe_unused,
+				 int subtest __maybe_unused)
+{
+	const char *zen3 = "AuthenticAMD-25-([245][[:xdigit:]]|[[:xdigit:]])";
+
+	/* Stepping specific entries. */
+	TEST_ASSERT_VAL("stepping match",
+			!strcmp_cpuid_str(EM_X86_64, "GenuineIntel-6-55-[01234]",
+					  "GenuineIntel-6-55-4"));
+	TEST_ASSERT_VAL("stepping mismatch",
+			strcmp_cpuid_str(EM_X86_64, "GenuineIntel-6-55-[56789ABCDEF]",
+					 "GenuineIntel-6-55-4"));
+	/* The stepping is ignored when the entry isn't stepping specific. */
+	TEST_ASSERT_VAL("ignore stepping",
+			!strcmp_cpuid_str(EM_X86_64, "GenuineIntel-6-(97|9A|B7|BA|BF)",
+					  "GenuineIntel-6-B7-1"));
+	TEST_ASSERT_VAL("model mismatch",
+			strcmp_cpuid_str(EM_X86_64, "GenuineIntel-6-(97|9A|B7|BA|BF)",
+					 "GenuineIntel-6-55-4"));
+	/* HEADER_CPUID is "vendor,family,model,step" in decimal, model 85 is 0x55. */
+	TEST_ASSERT_VAL("header stepping match",
+			!strcmp_cpuid_str(EM_X86_64, "GenuineIntel-6-55-[01234]",
+					  "GenuineIntel,6,85,4"));
+	TEST_ASSERT_VAL("header ignore stepping",
+			!strcmp_cpuid_str(EM_X86_64, zen3, "AuthenticAMD,25,33,2"));
+	/* 32-bit x86 uses the same comparison. */
+	TEST_ASSERT_VAL("i386", !strcmp_cpuid_str(EM_386, "GenuineIntel-6-55-[01234]",
+						  "GenuineIntel-6-55-4"));
+	return 0;
+}
+
+static int test__cpuid_match_powerpc(struct test_suite *test __maybe_unused,
+				     int subtest __maybe_unused)
+{
+	TEST_ASSERT_VAL("pvr match",
+			!strcmp_cpuid_str(EM_PPC64, "0x004e[[:xdigit:]]{4}", "0x004e0200"));
+	TEST_ASSERT_VAL("pvr mismatch",
+			strcmp_cpuid_str(EM_PPC64, "0x0080[[:xdigit:]]{4}", "0x004e0200"));
+	/* HEADER_CPUID is "pvr_ver,pvr_rev" in decimal. */
+	TEST_ASSERT_VAL("header pvr match",
+			!strcmp_cpuid_str(EM_PPC64, "0x004e[[:xdigit:]]{4}", "78,512"));
+	TEST_ASSERT_VAL("header pvr mismatch",
+			strcmp_cpuid_str(EM_PPC64, "0x0080[[:xdigit:]]{4}", "78,512"));
+	return 0;
+}
+
+static int test__cpuid_match_regex(struct test_suite *test __maybe_unused,
+				   int subtest __maybe_unused)
+{
+	/* Three hyphens in a RISC-V CPUID don't imply an x86 stepping. */
+	TEST_ASSERT_VAL("riscv match",
+			!strcmp_cpuid_str(EM_RISCV, "0x489-0x8000000000000007-0x[[:xdigit:]]+",
+					  "0x489-0x8000000000000007-0x0000000000000001"));
+	TEST_ASSERT_VAL("riscv mismatch",
+			strcmp_cpuid_str(EM_RISCV, "0x489-0x8000000000000008-0x[[:xdigit:]]+",
+					 "0x489-0x8000000000000007-0x0000000000000001"));
+	TEST_ASSERT_VAL("s390 match",
+			!strcmp_cpuid_str(EM_S390, "^IBM.209[78].*[13]\\.[1-5].[[:xdigit:]]+$",
+					  "IBM,2097,705,T01,1.3,002f"));
+	/* The whole CPUID must match. */
+	TEST_ASSERT_VAL("partial match",
+			strcmp_cpuid_str(EM_S390, "IBM,2097", "IBM,2097,705,T01,1.3,002f"));
+	return 0;
+}
+
 static struct test_case tests__cpuid_match[] = {
 	TEST_CASE("arm64 MIDR", cpuid_match_arm64),
+	TEST_CASE("Exact test and common tables", cpuid_match_exact),
+	TEST_CASE("x86", cpuid_match_x86),
+	TEST_CASE("PowerPC", cpuid_match_powerpc),
+	TEST_CASE("Regular expression", cpuid_match_regex),
 	{ .name = NULL, }
 };
 
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 17/30] perf dwarf-regs: Add e_machine__same_arch()
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (15 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 16/30] perf test: Add CPUID matching tests for other architectures Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:00   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 18/30] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric() Ian Rogers
                   ` (12 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

The 32-bit and 64-bit variants of some architectures have different ELF
machines, such as EM_386 and EM_X86_64, while others, such as RISC-V,
MIPS and PA-RISC, use one ELF machine for both. Code wanting to know if
two ELF machines are the same architecture either normalized the ELF
machine itself, as in pmu_events_map__e_machine_matches(), or compared
the strings from perf_env__arch(). perf_env__arch() maps EM_386 and
EM_X86_64 to "x86", but distinguishes "riscv" and "riscv64", "mips" and
"mips64", and "parisc" and "parisc64" by whether the kernel is 64-bit,
and computing it requires string lookups.

Add e_machine__64bit(), giving the ELF machine of the 64-bit variant of
an architecture, and e_machine__same_arch() next to EM_HOST. EM_ARM and
EM_AARCH64 remain distinct architectures. Use them when matching
pmu-events tables and in is_pmu_core_len(), which compares a perf_env's
architecture with the host's.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/pmu-events/empty-pmu-events.c |  6 +---
 tools/perf/pmu-events/jevents.py         |  6 +---
 tools/perf/util/include/dwarf-regs.h     | 37 ++++++++++++++++++++++++
 tools/perf/util/pmu.c                    |  5 +++-
 4 files changed, 43 insertions(+), 11 deletions(-)

diff --git a/tools/perf/pmu-events/empty-pmu-events.c b/tools/perf/pmu-events/empty-pmu-events.c
index 872c6ca6e817..3870356485ef 100644
--- a/tools/perf/pmu-events/empty-pmu-events.c
+++ b/tools/perf/pmu-events/empty-pmu-events.c
@@ -5783,11 +5783,7 @@ int pmu_metrics_table__find_metric(const struct pmu_metrics_table *table,
 static bool pmu_events_map__e_machine_matches(const struct pmu_events_map *map,
 					      uint16_t e_machine)
 {
-	if (e_machine == EM_386)
-		e_machine = EM_X86_64;
-	else if (e_machine == EM_PPC)
-		e_machine = EM_PPC64;
-	return map->e_machine == e_machine;
+	return e_machine__same_arch(map->e_machine, e_machine);
 }
 
 static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
diff --git a/tools/perf/pmu-events/jevents.py b/tools/perf/pmu-events/jevents.py
index 802ae2ad4405..6adb385b38e5 100755
--- a/tools/perf/pmu-events/jevents.py
+++ b/tools/perf/pmu-events/jevents.py
@@ -1201,11 +1201,7 @@ int pmu_metrics_table__find_metric(const struct pmu_metrics_table *table,
 static bool pmu_events_map__e_machine_matches(const struct pmu_events_map *map,
 \t\t\t\t\t      uint16_t e_machine)
 {
-\tif (e_machine == EM_386)
-\t\te_machine = EM_X86_64;
-\telse if (e_machine == EM_PPC)
-\t\te_machine = EM_PPC64;
-\treturn map->e_machine == e_machine;
+\treturn e_machine__same_arch(map->e_machine, e_machine);
 }
 
 static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
diff --git a/tools/perf/util/include/dwarf-regs.h b/tools/perf/util/include/dwarf-regs.h
index 1da915c2683a..07f285a94e03 100644
--- a/tools/perf/util/include/dwarf-regs.h
+++ b/tools/perf/util/include/dwarf-regs.h
@@ -85,6 +85,43 @@
   #define EF_HOST 0
 #endif
 
+/**
+ * e_machine__64bit() - The ELF machine of the 64-bit variant of an architecture.
+ * @e_machine: An ELF machine, such as EM_386.
+ *
+ * The 32-bit and 64-bit variants of some architectures have different ELF
+ * machines, for example EM_386 and EM_X86_64, whereas others, like RISC-V,
+ * MIPS and PA-RISC, use the same ELF machine for both. EM_ARM and EM_AARCH64
+ * are considered distinct architectures.
+ *
+ * Return: The 64-bit ELF machine, or @e_machine if there isn't one.
+ */
+static inline uint16_t e_machine__64bit(uint16_t e_machine)
+{
+	switch (e_machine) {
+	case EM_386:
+		return EM_X86_64;
+	case EM_PPC:
+		return EM_PPC64;
+	case EM_SPARC:
+	case EM_SPARC32PLUS:
+		return EM_SPARCV9;
+	default:
+		return e_machine;
+	}
+}
+
+/**
+ * e_machine__same_arch() - Are two ELF machines the same architecture,
+ *                          ignoring differences in word size?
+ * @a: An ELF machine.
+ * @b: Another ELF machine.
+ */
+static inline bool e_machine__same_arch(uint16_t a, uint16_t b)
+{
+	return e_machine__64bit(a) == e_machine__64bit(b);
+}
+
 #define DWARF_REG_PC  0xd3af9c /* random number */
 #define DWARF_REG_FB  0xd3affb /* random number */
 
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 06c4912d2f65..d5384c074265 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -34,6 +34,8 @@
 #include "print-events.h"
 #include "hashmap.h"
 #include "header.h"
+#include "dwarf-regs.h"
+#include "env.h"
 #include "string2.h"
 #include "strbuf.h"
 #include "fncache.h"
@@ -2092,7 +2094,8 @@ bool is_pmu_core_len(struct perf_env *env, const char *name, size_t len)
 	 * isn't portable, for example, big.LITTLE ARM and Apple core PMUs are
 	 * named after their CPU rather than "cpu_core" and "cpu_atom".
 	 */
-	if (env && strcmp(perf_env__arch(env), perf_env__arch(/*env=*/NULL))) {
+	if (env && !e_machine__same_arch(perf_env__e_machine(env, /*e_flags=*/NULL),
+					 perf_env__e_machine(host_env(), /*e_flags=*/NULL))) {
 		pr_debug("Can't identify core PMUs of a %s perf.data file recorded without hybrid topology\n",
 			 perf_env__arch(env));
 		return false;
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 18/30] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric()
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (16 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 17/30] perf dwarf-regs: Add e_machine__same_arch() Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:37   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 19/30] perf metricgroup: Move matching of metrics to recorded events from perf script Ian Rogers
                   ` (11 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

metricgroup__sys_event_iter() matched system metrics against uncore
PMUs using the live host's PMUs, so when processing a perf.data file
from another machine system metrics were matched against the wrong PMU
topology. Add a struct perf_pmus * argument to
metricgroup__for_each_metric() and use it to match the metric's compat
identifier. Callers pass the PMUs of the environment they are working
on.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/builtin-script.c    |  4 +++-
 tools/perf/util/metricgroup.c  | 12 +++++++-----
 tools/perf/util/metricgroup.h  | 14 ++++++++++++--
 tools/perf/util/print-events.c |  3 ++-
 4 files changed, 24 insertions(+), 9 deletions(-)

diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index 97bad639753f..f854eadb46eb 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2523,6 +2523,7 @@ static void perf_sample__fprint_metric(struct thread *thread,
 
 	if (!init_metrics) {
 		/* One time initialization of stat_config and metric data. */
+		struct perf_env *env = evlist__env(evsel->evlist) ?: host_env();
 		struct script_find_metrics_args args = {
 			.evlist = evsel->evlist,
 			.system_wide = perf_thread_map__pid(evsel->core.threads, /*idx=*/0) == -1,
@@ -2541,7 +2542,8 @@ static void perf_sample__fprint_metric(struct thread *thread,
 						  /*needs_sort=*/false);
 		}
 
-		metricgroup__for_each_metric(pmu_metrics_table__find(), script_find_metrics, &args);
+		metricgroup__for_each_metric(perf_env__pmus(env), pmu_metrics_table__find(),
+					     script_find_metrics, &args);
 		init_metrics = true;
 	}
 
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 80c2086555a8..fba02619ffb5 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -406,6 +406,7 @@ static bool match_pm_metric_or_groups(struct perf_pmus *pmus, const struct pmu_m
 }
 
 struct metricgroup_iter_data {
+	struct perf_pmus *pmus;
 	pmu_metric_iter_fn fn;
 	void *data;
 };
@@ -421,14 +422,15 @@ static int metricgroup__sys_event_iter(const struct pmu_metric *pm,
 		return 0;
 
 	/* Only process with the iterator if there is a a PMU that matches the ID. */
-	pmu = perf_pmus__scan_for_uncore_id(perf_env__pmus(host_env()), pmu, pm->compat);
+	pmu = perf_pmus__scan_for_uncore_id(d->pmus, pmu, pm->compat);
 	return pmu ? d->fn(pm, table, d->data) : 0;
 }
 
-int metricgroup__for_each_metric(const struct pmu_metrics_table *table, pmu_metric_iter_fn fn,
-				 void *data)
+int metricgroup__for_each_metric(struct perf_pmus *pmus, const struct pmu_metrics_table *table,
+				 pmu_metric_iter_fn fn, void *data)
 {
 	struct metricgroup_iter_data sys_data = {
+		.pmus = pmus,
 		.fn = fn,
 		.data = data,
 	};
@@ -1118,7 +1120,7 @@ static int metricgroup__add_metric(struct perf_pmus *pmus, const char *pmu,
 	 * Iterate over all metrics seeing if metric matches either the
 	 * name or group. When it does add the metric to the list.
 	 */
-	ret = metricgroup__for_each_metric(table, metricgroup__add_metric_callback, &data);
+	ret = metricgroup__for_each_metric(pmus, table, metricgroup__add_metric_callback, &data);
 	if (!ret && !data.has_match)
 		ret = -ENOENT;
 
@@ -1656,7 +1658,7 @@ bool metricgroup__has_metric_or_groups(struct perf_pmus *pmus, const char *pmu,
 		.metric_or_groups = metric_or_groups,
 	};
 
-	return metricgroup__for_each_metric(table,
+	return metricgroup__for_each_metric(pmus, table,
 					    metricgroup__has_metric_or_groups_callback,
 					    &data)
 		? true : false;
diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
index bc5d3d21f405..2cc28b3eb983 100644
--- a/tools/perf/util/metricgroup.h
+++ b/tools/perf/util/metricgroup.h
@@ -85,8 +85,18 @@ int metricgroup__parse_groups_test(struct evlist *evlist,
 				   const char *str,
 				   bool cputype_filter);
 
-int metricgroup__for_each_metric(const struct pmu_metrics_table *table, pmu_metric_iter_fn fn,
-				 void *data);
+/**
+ * metricgroup__for_each_metric() - Iterate over metrics in @table and system
+ *                                  metrics matching @pmus.
+ * @pmus: The PMUs used to match system metric compat identifiers.
+ * @table: Optional CPU metrics table to iterate over.
+ * @fn: Callback invoked for each metric.
+ * @data: User data passed to @fn.
+ *
+ * Return: 0 on success or the non-zero return value from @fn.
+ */
+int metricgroup__for_each_metric(struct perf_pmus *pmus, const struct pmu_metrics_table *table,
+				 pmu_metric_iter_fn fn, void *data);
 bool metricgroup__has_metric_or_groups(struct perf_pmus *pmus, const char *pmu,
 				       const char *metric_or_groups);
 unsigned int metricgroups__topdown_max_level(void);
diff --git a/tools/perf/util/print-events.c b/tools/perf/util/print-events.c
index fe48efe9aefb..6145367d2184 100644
--- a/tools/perf/util/print-events.c
+++ b/tools/perf/util/print-events.c
@@ -297,7 +297,8 @@ void metricgroup__print(const struct print_callbacks *print_cb, void *print_stat
 	groups.node_cmp = mep_cmp;
 	groups.node_delete = mep_delete;
 
-	metricgroup__for_each_metric(table, metricgroup__add_to_mep_groups_callback, &groups);
+	metricgroup__for_each_metric(perf_env__pmus(host_env()), table,
+				     metricgroup__add_to_mep_groups_callback, &groups);
 
 	for (node = rb_first_cached(&groups.entries); node; node = next) {
 		struct mep *me = container_of(node, struct mep, nd);
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 19/30] perf metricgroup: Move matching of metrics to recorded events from perf script
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (17 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 18/30] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric() Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:59   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 20/30] perf parse-events: Decode metric-id terms when adding events Ian Rogers
                   ` (10 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

perf script finds metrics whose events are all present in the
perf.data file's evlist so that they can be displayed alongside
samples. Move this code into metricgroup.c as
metricgroup__add_matching_metrics() so that it can be shared with
other commands, such as perf stat report. No functional change.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/builtin-script.c   | 156 +--------------------------------
 tools/perf/util/metricgroup.c | 157 ++++++++++++++++++++++++++++++++++
 tools/perf/util/metricgroup.h |  12 +++
 3 files changed, 172 insertions(+), 153 deletions(-)

diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index f854eadb46eb..afed2353ef80 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2350,151 +2350,6 @@ static void script_new_line(struct perf_stat_config *config __maybe_unused,
 	/* Each metric is printed on its own line by script_print_metric(). */
 }
 
-struct script_find_metrics_args {
-	struct evlist *evlist;
-	bool system_wide;
-};
-
-static struct evsel *map_metric_evsel_to_script_evsel(struct evlist *script_evlist,
-						      struct evsel *metric_evsel)
-{
-	struct evsel *script_evsel;
-
-	evlist__for_each_entry(script_evlist, script_evsel) {
-		/* Skip if perf_event_attr differ. */
-		if (metric_evsel->core.attr.type != script_evsel->core.attr.type)
-			continue;
-		if (metric_evsel->core.attr.config != script_evsel->core.attr.config)
-			continue;
-		/* Skip if the script event has a metric_id that doesn't match. */
-		if (script_evsel->metric_id &&
-		    strcmp(evsel__metric_id(metric_evsel), evsel__metric_id(script_evsel))) {
-			pr_debug("Skipping matching evsel due to differing metric ids '%s' vs '%s'\n",
-				 evsel__metric_id(metric_evsel), evsel__metric_id(script_evsel));
-			continue;
-		}
-		return script_evsel;
-	}
-	return NULL;
-}
-
-static int script_find_metrics(const struct pmu_metric *pm,
-			       const struct pmu_metrics_table *table __maybe_unused,
-			       void *data)
-{
-	struct script_find_metrics_args *args = data;
-	struct evlist *script_evlist = args->evlist;
-	struct evlist *metric_evlist = evlist__new();
-	struct evsel *metric_evsel;
-	int ret = metricgroup__parse_groups(metric_evlist,
-					/*pmu=*/"all",
-					/*cputype_filter=*/false,
-					pm->metric_name,
-					/*metric_no_group=*/false,
-					/*metric_no_merge=*/false,
-					/*metric_no_threshold=*/true,
-					/*user_requested_cpu_list=*/NULL,
-					args->system_wide,
-					/*hardware_aware_grouping=*/false);
-
-	if (ret) {
-		/* Metric parsing failed but continue the search. */
-		goto out;
-	}
-
-	/*
-	 * Check the script_evlist has an entry for each metric_evlist entry. If
-	 * the script evsel was already set up avoid changing data that may
-	 * break it.
-	 */
-	evlist__for_each_entry(metric_evlist, metric_evsel) {
-		struct evsel *script_evsel =
-			map_metric_evsel_to_script_evsel(script_evlist, metric_evsel);
-		struct evsel *new_metric_leader;
-
-		if (!script_evsel) {
-			pr_debug("Skipping metric '%s' as evsel '%s' / '%s' is missing\n",
-				pm->metric_name, evsel__name(metric_evsel),
-				evsel__metric_id(metric_evsel));
-			goto out;
-		}
-
-		if (script_evsel->metric_leader == NULL)
-			continue;
-
-		if (metric_evsel->metric_leader == metric_evsel) {
-			new_metric_leader = script_evsel;
-		} else {
-			new_metric_leader =
-				map_metric_evsel_to_script_evsel(script_evlist,
-								 metric_evsel->metric_leader);
-		}
-		/* Mismatching evsel leaders. */
-		if (script_evsel->metric_leader != new_metric_leader) {
-			pr_debug("Skipping metric '%s' due to mismatching evsel metric leaders '%s' vs '%s'\n",
-				pm->metric_name, evsel__metric_id(metric_evsel),
-				evsel__metric_id(script_evsel));
-			goto out;
-		}
-	}
-	/*
-	 * Metric events match those in the script evlist, copy metric evsel
-	 * data into the script evlist.
-	 */
-	evlist__for_each_entry(metric_evlist, metric_evsel) {
-		struct evsel *script_evsel =
-			map_metric_evsel_to_script_evsel(script_evlist, metric_evsel);
-		struct metric_event *metric_me =
-			metricgroup__lookup(evlist__metric_events(metric_evlist),
-					    metric_evsel,
-					    /*create=*/false);
-
-		if (script_evsel->metric_id == NULL) {
-			script_evsel->metric_id = metric_evsel->metric_id;
-			metric_evsel->metric_id = NULL;
-		}
-
-		if (script_evsel->metric_leader == NULL) {
-			if (metric_evsel->metric_leader == metric_evsel) {
-				script_evsel->metric_leader = script_evsel;
-			} else {
-				script_evsel->metric_leader =
-					map_metric_evsel_to_script_evsel(script_evlist,
-								       metric_evsel->metric_leader);
-			}
-		}
-
-		if (metric_me) {
-			struct metric_expr *expr;
-			struct metric_event *script_me =
-				metricgroup__lookup(evlist__metric_events(script_evlist),
-						    script_evsel,
-						    /*create=*/true);
-
-			if (!script_me) {
-				/*
-				 * As the metric_expr is created, the only
-				 * failure is a lack of memory.
-				 */
-				goto out;
-			}
-			list_splice_init(&metric_me->head, &script_me->head);
-			list_for_each_entry(expr, &script_me->head, nd) {
-				for (int i = 0; expr->metric_events[i]; i++) {
-					expr->metric_events[i] =
-						map_metric_evsel_to_script_evsel(script_evlist,
-									expr->metric_events[i]);
-				}
-			}
-		}
-	}
-	pr_debug("Found metric '%s' whose evsels match those of in the perf data\n",
-		 pm->metric_name);
-out:
-	evlist__put(metric_evlist);
-	return 0;
-}
-
 static struct aggr_cpu_id script_aggr_cpu_id_get(struct perf_stat_config *config __maybe_unused,
 						    struct perf_cpu cpu)
 {
@@ -2523,12 +2378,6 @@ static void perf_sample__fprint_metric(struct thread *thread,
 
 	if (!init_metrics) {
 		/* One time initialization of stat_config and metric data. */
-		struct perf_env *env = evlist__env(evsel->evlist) ?: host_env();
-		struct script_find_metrics_args args = {
-			.evlist = evsel->evlist,
-			.system_wide = perf_thread_map__pid(evsel->core.threads, /*idx=*/0) == -1,
-
-		};
 		if (!stat_config.output)
 			stat_config.output = stdout;
 
@@ -2542,8 +2391,9 @@ static void perf_sample__fprint_metric(struct thread *thread,
 						  /*needs_sort=*/false);
 		}
 
-		metricgroup__for_each_metric(perf_env__pmus(env), pmu_metrics_table__find(),
-					     script_find_metrics, &args);
+		metricgroup__add_matching_metrics(evsel->evlist,
+						  perf_thread_map__pid(evsel->core.threads,
+								       /*idx=*/0) == -1);
 		init_metrics = true;
 	}
 
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index fba02619ffb5..9922445a6d15 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -453,6 +453,163 @@ int metricgroup__for_each_metric(struct perf_pmus *pmus, const struct pmu_metric
 	return pmu_for_each_sys_metric(metricgroup__sys_event_iter, &sys_data);
 }
 
+struct find_matching_metrics_args {
+	struct evlist *evlist;
+	bool system_wide;
+};
+
+static struct evsel *map_metric_evsel_to_evlist(struct evlist *data_evlist,
+						struct evsel *metric_evsel)
+{
+	struct evsel *data_evsel;
+
+	evlist__for_each_entry(data_evlist, data_evsel) {
+		/* Skip if perf_event_attr differ. */
+		if (metric_evsel->core.attr.type != data_evsel->core.attr.type)
+			continue;
+		if (metric_evsel->core.attr.config != data_evsel->core.attr.config)
+			continue;
+		/* Skip if the data event has a metric_id that doesn't match. */
+		if (data_evsel->metric_id &&
+		    strcmp(evsel__metric_id(metric_evsel), evsel__metric_id(data_evsel))) {
+			pr_debug("Skipping matching evsel due to differing metric ids '%s' vs '%s'\n",
+				 evsel__metric_id(metric_evsel), evsel__metric_id(data_evsel));
+			continue;
+		}
+		return data_evsel;
+	}
+	return NULL;
+}
+
+static int find_matching_metrics_cb(const struct pmu_metric *pm,
+				    const struct pmu_metrics_table *table __maybe_unused,
+				    void *data)
+{
+	struct find_matching_metrics_args *args = data;
+	struct evlist *data_evlist = args->evlist;
+	struct evlist *metric_evlist = evlist__new();
+	struct evsel *metric_evsel;
+	int ret = metricgroup__parse_groups(metric_evlist,
+					/*pmu=*/"all",
+					/*cputype_filter=*/false,
+					pm->metric_name,
+					/*metric_no_group=*/false,
+					/*metric_no_merge=*/false,
+					/*metric_no_threshold=*/true,
+					/*user_requested_cpu_list=*/NULL,
+					args->system_wide,
+					/*hardware_aware_grouping=*/false);
+
+	if (ret) {
+		/* Metric parsing failed but continue the search. */
+		goto out;
+	}
+
+	/*
+	 * Check the data_evlist has an entry for each metric_evlist entry. If
+	 * the data evsel was already set up avoid changing data that may
+	 * break it.
+	 */
+	evlist__for_each_entry(metric_evlist, metric_evsel) {
+		struct evsel *data_evsel =
+			map_metric_evsel_to_evlist(data_evlist, metric_evsel);
+		struct evsel *new_metric_leader;
+
+		if (!data_evsel) {
+			pr_debug("Skipping metric '%s' as evsel '%s' / '%s' is missing\n",
+				pm->metric_name, evsel__name(metric_evsel),
+				evsel__metric_id(metric_evsel));
+			goto out;
+		}
+
+		if (data_evsel->metric_leader == NULL)
+			continue;
+
+		if (metric_evsel->metric_leader == metric_evsel) {
+			new_metric_leader = data_evsel;
+		} else {
+			new_metric_leader =
+				map_metric_evsel_to_evlist(data_evlist,
+								 metric_evsel->metric_leader);
+		}
+		/* Mismatching evsel leaders. */
+		if (data_evsel->metric_leader != new_metric_leader) {
+			pr_debug("Skipping metric '%s' due to mismatching evsel metric leaders '%s' vs '%s'\n",
+				pm->metric_name, evsel__metric_id(metric_evsel),
+				evsel__metric_id(data_evsel));
+			goto out;
+		}
+	}
+	/*
+	 * Metric events match those in the data evlist, copy metric evsel
+	 * data into the data evlist.
+	 */
+	evlist__for_each_entry(metric_evlist, metric_evsel) {
+		struct evsel *data_evsel =
+			map_metric_evsel_to_evlist(data_evlist, metric_evsel);
+		struct metric_event *metric_me =
+			metricgroup__lookup(evlist__metric_events(metric_evlist),
+					    metric_evsel,
+					    /*create=*/false);
+
+		if (data_evsel->metric_id == NULL) {
+			data_evsel->metric_id = metric_evsel->metric_id;
+			metric_evsel->metric_id = NULL;
+		}
+
+		if (data_evsel->metric_leader == NULL) {
+			if (metric_evsel->metric_leader == metric_evsel) {
+				data_evsel->metric_leader = data_evsel;
+			} else {
+				data_evsel->metric_leader =
+					map_metric_evsel_to_evlist(data_evlist,
+								       metric_evsel->metric_leader);
+			}
+		}
+
+		if (metric_me) {
+			struct metric_expr *expr;
+			struct metric_event *data_me =
+				metricgroup__lookup(evlist__metric_events(data_evlist),
+						    data_evsel,
+						    /*create=*/true);
+
+			if (!data_me) {
+				/*
+				 * As the metric_expr is created, the only
+				 * failure is a lack of memory.
+				 */
+				goto out;
+			}
+			list_splice_init(&metric_me->head, &data_me->head);
+			list_for_each_entry(expr, &data_me->head, nd) {
+				for (int i = 0; expr->metric_events[i]; i++) {
+					expr->metric_events[i] =
+						map_metric_evsel_to_evlist(data_evlist,
+									expr->metric_events[i]);
+				}
+			}
+		}
+	}
+	pr_debug("Found metric '%s' whose evsels match those of in the perf data\n",
+		 pm->metric_name);
+out:
+	evlist__put(metric_evlist);
+	return 0;
+}
+
+int metricgroup__add_matching_metrics(struct evlist *evlist, bool system_wide)
+{
+	struct perf_env *env = evlist__env(evlist) ?: host_env();
+	struct find_matching_metrics_args args = {
+		.evlist = evlist,
+		.system_wide = system_wide,
+	};
+
+	return metricgroup__for_each_metric(perf_env__pmus(env), pmu_metrics_table__find(),
+					    find_matching_metrics_cb, &args);
+}
+
 static const char *code_characters = ",-=@";
 
 static int encode_metric_id(struct strbuf *sb, const char *x)
diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
index 2cc28b3eb983..2069211913dc 100644
--- a/tools/perf/util/metricgroup.h
+++ b/tools/perf/util/metricgroup.h
@@ -97,6 +97,18 @@ int metricgroup__parse_groups_test(struct evlist *evlist,
  */
 int metricgroup__for_each_metric(struct perf_pmus *pmus, const struct pmu_metrics_table *table,
 				 pmu_metric_iter_fn fn, void *data);
+/**
+ * metricgroup__add_matching_metrics() - Add metrics whose events are in @evlist.
+ * @evlist: The evlist, typically read from a perf.data file, to add metrics to.
+ * @system_wide: Are the events in @evlist system wide?
+ *
+ * For every metric whose events all have a corresponding evsel in @evlist,
+ * add the metric to the metric events of @evlist so that it is computed and
+ * displayed along with the counts of the evsels.
+ *
+ * Return: 0 on success or a negative error code.
+ */
+int metricgroup__add_matching_metrics(struct evlist *evlist, bool system_wide);
 bool metricgroup__has_metric_or_groups(struct perf_pmus *pmus, const char *pmu,
 				       const char *metric_or_groups);
 unsigned int metricgroups__topdown_max_level(void);
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 20/30] perf parse-events: Decode metric-id terms when adding events
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (18 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 19/30] perf metricgroup: Move matching of metrics to recorded events from perf script Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:33   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 21/30] perf evsel: Generalize metric_id to a set of metric_ids Ian Rogers
                   ` (9 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Metric events are parsed from an event string where the identifier from
the metric expression is passed as a metric-id term. As characters such
as '@' and ',' can't appear in a term value the metricgroup code encodes
them and, after parsing, walked the parsed evlist replacing each evsel's
metric_id with its decoded version.

Move the encoding and decoding to parse-events.c, adding
parse_events__encode_metric_id() for the metricgroup code, and store the
decoded metric-id on the evsel when it is created in __add_event(). This
avoids rewriting the metric_id of evsels after parsing, leaving
metricgroup with only the replacement of the evsel names of metric
events by friendlier versions of the metric-id. While moving the
decoding, fix the out of bounds check on the encoded character index
and return -EINVAL for a bad encoding.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/metricgroup.c  | 110 ++++++++-------------------------
 tools/perf/util/parse-events.c |  82 +++++++++++++++++++++++-
 tools/perf/util/parse-events.h |   1 +
 3 files changed, 108 insertions(+), 85 deletions(-)

diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 9922445a6d15..08679392bf6d 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -610,57 +610,13 @@ int metricgroup__add_matching_metrics(struct evlist *evlist, bool system_wide)
 					    find_matching_metrics_cb, &args);
 }
 
-static const char *code_characters = ",-=@";
-
-static int encode_metric_id(struct strbuf *sb, const char *x)
-{
-	int ret = 0;
-
-	for (; *x; x++) {
-		const char *c = strchr(code_characters, *x);
-		if (c) {
-			ret = strbuf_addch(sb, '!');
-			if (ret)
-				break;
-
-			ret = strbuf_addch(sb, '0' + (c - code_characters));
-			if (ret)
-				break;
-		} else {
-			ret = strbuf_addch(sb, *x);
-			if (ret)
-				break;
-		}
-	}
-	return ret;
-}
-
-static int decode_metric_id(struct strbuf *sb, const char *x)
-{
-	const char *orig = x;
-	size_t i;
-	char c;
-	int ret;
-
-	for (; *x; x++) {
-		c = *x;
-		if (*x == '!') {
-			x++;
-			i = *x - '0';
-			if (i > strlen(code_characters)) {
-				pr_err("Bad metric-id encoding in: '%s'", orig);
-				return -1;
-			}
-			c = code_characters[i];
-		}
-		ret = strbuf_addch(sb, c);
-		if (ret)
-			return ret;
-	}
-	return 0;
-}
-
-static int decode_all_metric_ids(struct evlist *perf_evlist, const char *modifier)
+/*
+ * The metric events are parsed with a metric-id holding the identifier used in
+ * the metric expression. If the evsel name is just the parsed event, which
+ * contains the encoded metric-id, use the metric-id to give a more friendly
+ * display version.
+ */
+static int set_metric_event_names(struct evlist *perf_evlist, const char *modifier)
 {
 	struct evsel *ev;
 	struct strbuf sb = STRBUF_INIT;
@@ -668,51 +624,39 @@ static int decode_all_metric_ids(struct evlist *perf_evlist, const char *modifie
 	int ret = 0;
 
 	evlist__for_each_entry(perf_evlist, ev) {
-		if (!ev->metric_id)
+		bool has_slash = false;
+
+		if (!ev->metric_id || !strstr(ev->name, "metric-id="))
 			continue;
 
 		ret = strbuf_setlen(&sb, 0);
 		if (ret)
 			break;
 
-		ret = decode_metric_id(&sb, ev->metric_id);
+		ret = strbuf_addstr(&sb, ev->metric_id);
 		if (ret)
 			break;
 
-		free((char *)ev->metric_id);
-		ev->metric_id = strdup(sb.buf);
-		if (!ev->metric_id) {
-			ret = -ENOMEM;
-			break;
+		zfree(&ev->name);
+		for (cur = strchr(sb.buf, '@') ; cur; cur = strchr(++cur, '@')) {
+			*cur = '/';
+			has_slash = true;
 		}
-		/*
-		 * If the name is just the parsed event, use the metric-id to
-		 * give a more friendly display version.
-		 */
-		if (strstr(ev->name, "metric-id=")) {
-			bool has_slash = false;
-
-			zfree(&ev->name);
-			for (cur = strchr(sb.buf, '@') ; cur; cur = strchr(++cur, '@')) {
-				*cur = '/';
-				has_slash = true;
-			}
 
-			if (modifier) {
-				if (!has_slash && !strchr(sb.buf, ':')) {
-					ret = strbuf_addch(&sb, ':');
-					if (ret)
-						break;
-				}
-				ret = strbuf_addstr(&sb, modifier);
+		if (modifier) {
+			if (!has_slash && !strchr(sb.buf, ':')) {
+				ret = strbuf_addch(&sb, ':');
 				if (ret)
 					break;
 			}
-			ev->name = strdup(sb.buf);
-			if (!ev->name) {
-				ret = -ENOMEM;
+			ret = strbuf_addstr(&sb, modifier);
+			if (ret)
 				break;
-			}
+		}
+		ev->name = strdup(sb.buf);
+		if (!ev->name) {
+			ret = -ENOMEM;
+			break;
 		}
 	}
 	strbuf_release(&sb);
@@ -787,7 +731,7 @@ static int metricgroup__build_event_string(struct strbuf *events,
 			ret = strbuf_addstr(events, "/metric-id=");
 			RETURN_IF_NON_ZERO(ret);
 		}
-		ret = encode_metric_id(events, id);
+		ret = parse_events__encode_metric_id(events, id);
 		RETURN_IF_NON_ZERO(ret);
 		ret = strbuf_addstr(events, "/");
 		RETURN_IF_NON_ZERO(ret);
@@ -1514,7 +1458,7 @@ static int parse_ids(bool metric_no_merge, bool fake_pmu,
 		parse_events_error__print(&parse_error, events.buf);
 		goto err_out;
 	}
-	ret = decode_all_metric_ids(parsed_evlist, modifier);
+	ret = set_metric_event_names(parsed_evlist, modifier);
 	if (ret)
 		goto err_out;
 
diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c
index 159d847f44ce..a640344afcc8 100644
--- a/tools/perf/util/parse-events.c
+++ b/tools/perf/util/parse-events.c
@@ -171,6 +171,73 @@ static void fix_raw(struct parse_events_terms *config_terms, struct perf_pmu *pm
 	}
 }
 
+/*
+ * Characters that can't appear in a metric-id term value are encoded as '!'
+ * followed by the character's index in this string.
+ */
+static const char *metric_id_code_characters = ",-=@";
+
+/**
+ * parse_events__encode_metric_id - Encode a metric identifier as a metric-id
+ *                                  term value.
+ * @sb: The buffer the encoded identifier is appended to.
+ * @x: The metric identifier to encode.
+ *
+ * Return: 0 on success or the strbuf error.
+ */
+int parse_events__encode_metric_id(struct strbuf *sb, const char *x)
+{
+	int ret = 0;
+
+	for (; *x; x++) {
+		const char *c = strchr(metric_id_code_characters, *x);
+
+		if (c) {
+			ret = strbuf_addch(sb, '!');
+			if (ret)
+				break;
+
+			ret = strbuf_addch(sb, '0' + (c - metric_id_code_characters));
+			if (ret)
+				break;
+		} else {
+			ret = strbuf_addch(sb, *x);
+			if (ret)
+				break;
+		}
+	}
+	return ret;
+}
+
+/*
+ * Decode a metric-id term value encoded by parse_events__encode_metric_id()
+ * into @sb.
+ */
+static int parse_events__decode_metric_id(struct strbuf *sb, const char *x)
+{
+	const char *orig = x;
+	size_t i;
+	char c;
+	int ret;
+
+	for (; *x; x++) {
+		c = *x;
+		if (*x == '!') {
+			x++;
+			i = *x - '0';
+			if (i >= strlen(metric_id_code_characters)) {
+				pr_err("Bad metric-id encoding in: '%s'", orig);
+				return -EINVAL;
+			}
+			c = metric_id_code_characters[i];
+		}
+		ret = strbuf_addch(sb, c);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
 static struct evsel *
 __add_event(struct list_head *list, int *idx,
 	    struct perf_event_attr *attr,
@@ -261,7 +328,12 @@ __add_event(struct list_head *list, int *idx,
 	}
 
 	if (metric_id) {
-		evsel->metric_id = strdup(metric_id);
+		struct strbuf sb = STRBUF_INIT;
+		int ret = parse_events__decode_metric_id(&sb, metric_id);
+
+		if (!ret)
+			evsel->metric_id = strdup(sb.buf);
+		strbuf_release(&sb);
 		if (!evsel->metric_id)
 			goto out_err;
 	}
@@ -645,6 +717,7 @@ int parse_events_add_breakpoint(struct parse_events_state *parse_state,
 	struct perf_event_attr attr;
 	LIST_HEAD(config_terms);
 	const char *name;
+	int ret;
 
 	memset(&attr, 0, sizeof(attr));
 	attr.bp_addr = addr;
@@ -675,8 +748,11 @@ int parse_events_add_breakpoint(struct parse_events_state *parse_state,
 
 	name = get_config_name(head_config);
 
-	return add_event(list, parse_state, &attr, name, /*mertic_id=*/NULL,
+	ret = add_event(list, parse_state, &attr, name, /*mertic_id=*/NULL,
 			&config_terms, /*alternate_hw_config=*/PERF_COUNT_HW_MAX);
+	/* Free the config terms if they weren't moved to an evsel. */
+	free_config_terms(&config_terms);
+	return ret;
 }
 
 static int check_type_val(struct parse_events_term *term,
@@ -1601,6 +1677,8 @@ static int parse_events_add_pmu(struct parse_events_state *parse_state,
 			    &config_terms, first_wildcard_match, term_cpu, alternate_hw_config);
 	perf_cpu_map__put(term_cpu);
 	if (!evsel) {
+		/* The config terms weren't moved to an evsel. */
+		free_config_terms(&config_terms);
 		parse_events_terms__exit(&parsed_terms);
 		return -ENOMEM;
 	}
diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h
index 57eabdb97890..bec75c2d7b32 100644
--- a/tools/perf/util/parse-events.h
+++ b/tools/perf/util/parse-events.h
@@ -245,6 +245,7 @@ int parse_events_add_breakpoint(struct parse_events_state *parse_state,
 struct evsel *parse_events__add_event(int idx, struct perf_event_attr *attr,
 				      const char *name, const char *metric_id,
 				      struct perf_pmu *pmu);
+int parse_events__encode_metric_id(struct strbuf *sb, const char *x);
 
 int parse_events_multi_pmu_add(struct parse_events_state *parse_state,
 			       const char *event_name,
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 21/30] perf evsel: Generalize metric_id to a set of metric_ids
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (19 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 20/30] perf parse-events: Decode metric-id terms when adding events Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:31   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 22/30] perf pmu: Add perf_pmu__for_each_name_from_config() Ian Rogers
                   ` (8 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

When computing a metric the value of each of its events is added to the
expression context using the event's metric_id. When metrics are matched
to events read from a perf.data file, different metrics may refer to the
same recorded event by different names, for example "instructions" and
"cpu_core@INST_RETIRED.ANY@", but an evsel previously stored only a
single metric_id.

Replace evsel::metric_id with a NULL-terminated evsel::metric_ids set of
unique identifier strings. Keep the representation private to evsel.c
behind helpers: evsel__add_metric_id(), evsel__copy_metric_ids(),
evsel__has_metric_id(), evsel__shares_metric_id() and
evsel__for_each_metric_id(), a callback based iterator. Use the iterator
in prepare_metric() to populate the expression parse context with all
metric_ids of each metric event. The metric_ids are freed by
evsel__exit(), so make the __add_event() error path use evsel__put()
rather than freeing the evsel's strings itself.

Remove evsel__metric_id() as returning a single identifier from the set
is error prone. Its uses either iterate over the set, test membership,
or, when finding duplicate events for a metric leader, test whether two
evsels share an identifier. Tool events without an explicit metric-id
keep their tool event name as an implicit identifier, other events
without a metric-id no longer have the identifier "unknown".

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/evsel.c        | 172 ++++++++++++++++++++++++++++++---
 tools/perf/util/evsel.h        |  10 +-
 tools/perf/util/metricgroup.c  |  98 ++++++++++++-------
 tools/perf/util/parse-events.c |   8 +-
 tools/perf/util/python.c       |  34 +++++--
 tools/perf/util/stat-shadow.c  |  37 +++++--
 6 files changed, 291 insertions(+), 68 deletions(-)

diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 9d58b9bfa8b6..b08f1cf9de0f 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -577,11 +577,8 @@ struct evsel *evsel__clone(struct evsel *orig)
 		if (evsel->filter == NULL)
 			goto out_err;
 	}
-	if (orig->metric_id) {
-		evsel->metric_id = strdup(orig->metric_id);
-		if (evsel->metric_id == NULL)
-			goto out_err;
-	}
+	if (evsel__copy_metric_ids(evsel, orig))
+		goto out_err;
 	evsel->cgrp = cgroup__get(orig->cgrp);
 #ifdef HAVE_LIBTRACEEVENT
 	if (orig->tp_sys) {
@@ -1029,15 +1026,166 @@ bool evsel__name_is(struct evsel *evsel, const char *name)
 	return !strcmp(evsel__name(evsel), name);
 }
 
-const char *evsel__metric_id(const struct evsel *evsel)
+/*
+ * Tool events, such as duration_time, are identified by their tool event name.
+ * Other events without a metric-id have no identifier. The evsel's PMU may not
+ * be set yet, such as during parsing or cloning, so check the attribute's type
+ * rather than using evsel__is_tool().
+ */
+static const char *evsel__implicit_metric_id(const struct evsel *evsel)
 {
-	if (evsel->metric_id)
-		return evsel->metric_id;
+	if (evsel->core.attr.type != PERF_PMU_TYPE_TOOL)
+		return NULL;
 
-	if (evsel__is_tool(evsel))
-		return evsel__tool_pmu_event_name(evsel);
+	return evsel__tool_pmu_event_name(evsel);
+}
 
-	return "unknown";
+/**
+ * evsel__for_each_metric_id - Call @cb for each metric identifier of @evsel.
+ * @evsel: The evsel holding the metric identifiers.
+ * @cb: Callback passed each identifier, a non-zero return stops the iteration.
+ * @data: Passed to @cb.
+ *
+ * Explicit identifiers are visited in the order they were added, followed by
+ * the tool event name for tool events. Tool events without explicit
+ * identifiers have their tool event name as the only identifier.
+ *
+ * Return: 0 or the first non-zero value returned by @cb.
+ */
+int evsel__for_each_metric_id(const struct evsel *evsel,
+			      int (*cb)(const char *metric_id, void *data),
+			      void *data)
+{
+	const char *implicit_id;
+
+	if (!evsel->metric_ids) {
+		implicit_id = evsel__implicit_metric_id(evsel);
+		return implicit_id ? cb(implicit_id, data) : 0;
+	}
+
+	for (size_t i = 0; evsel->metric_ids[i]; i++) {
+		int ret = cb(evsel->metric_ids[i], data);
+
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int metric_id_equal_cb(const char *metric_id, void *data)
+{
+	return !strcmp(metric_id, data);
+}
+
+/**
+ * evsel__has_metric_id - Is @metric_id an identifier of @evsel?
+ * @evsel: The evsel holding the metric identifiers.
+ * @metric_id: The identifier to search for, may be NULL.
+ *
+ * Return: true if @metric_id is in the set of metric identifiers of @evsel.
+ */
+bool evsel__has_metric_id(const struct evsel *evsel, const char *metric_id)
+{
+	if (!metric_id)
+		return false;
+
+	return evsel__for_each_metric_id(evsel, metric_id_equal_cb, (void *)metric_id);
+}
+
+static int has_metric_id_cb(const char *metric_id, void *data)
+{
+	const struct evsel *evsel = data;
+
+	return evsel__has_metric_id(evsel, metric_id);
+}
+
+/**
+ * evsel__shares_metric_id - Do two evsels have a metric identifier in common?
+ * @a: The first evsel.
+ * @b: The second evsel.
+ *
+ * Return: true if the intersection of the metric identifier sets of @a and @b
+ * isn't empty.
+ */
+bool evsel__shares_metric_id(const struct evsel *a, const struct evsel *b)
+{
+	return evsel__for_each_metric_id(a, has_metric_id_cb, (void *)b);
+}
+
+/**
+ * evsel__add_metric_id - Add an identifier to the metric identifiers of @evsel.
+ * @evsel: The evsel to add the metric identifier to.
+ * @metric_id: The identifier to add, ignored if NULL or already present.
+ *
+ * The first explicit identifier added to a tool event is followed by the tool
+ * event name so that the tool event name remains an identifier.
+ *
+ * Return: 0 on success or -ENOMEM.
+ */
+int evsel__add_metric_id(struct evsel *evsel, const char *metric_id)
+{
+	const char *implicit_id;
+	char **new_ids;
+	size_t nr = 0;
+
+	if (!metric_id || evsel__has_metric_id(evsel, metric_id))
+		return 0;
+
+	implicit_id = evsel->metric_ids ? NULL : evsel__implicit_metric_id(evsel);
+	while (evsel->metric_ids && evsel->metric_ids[nr])
+		nr++;
+
+	new_ids = realloc(evsel->metric_ids, (nr + 3) * sizeof(*new_ids));
+	if (!new_ids)
+		return -ENOMEM;
+	evsel->metric_ids = new_ids;
+
+	new_ids[nr] = strdup(metric_id);
+	if (!new_ids[nr])
+		goto out_err;
+	new_ids[nr + 1] = NULL;
+	if (implicit_id) {
+		new_ids[nr + 1] = strdup(implicit_id);
+		if (!new_ids[nr + 1])
+			goto out_err;
+		new_ids[nr + 2] = NULL;
+	}
+	return 0;
+out_err:
+	zfree(&new_ids[nr]);
+	if (!nr)
+		zfree(&evsel->metric_ids);
+	return -ENOMEM;
+}
+
+static int add_metric_id_cb(const char *metric_id, void *data)
+{
+	struct evsel *evsel = data;
+
+	return evsel__add_metric_id(evsel, metric_id);
+}
+
+/**
+ * evsel__copy_metric_ids - Add the metric identifiers of @src to @dst.
+ * @dst: The evsel to add the metric identifiers to.
+ * @src: The evsel whose metric identifiers are copied.
+ *
+ * Return: 0 on success or -ENOMEM.
+ */
+int evsel__copy_metric_ids(struct evsel *dst, const struct evsel *src)
+{
+	return evsel__for_each_metric_id(src, add_metric_id_cb, dst);
+}
+
+/**
+ * evsel__free_metric_ids - Free the metric identifiers of @evsel.
+ * @evsel: The evsel whose metric identifiers are freed.
+ */
+static void evsel__free_metric_ids(struct evsel *evsel)
+{
+	for (size_t i = 0; evsel->metric_ids && evsel->metric_ids[i]; i++)
+		zfree(&evsel->metric_ids[i]);
+	zfree(&evsel->metric_ids);
 }
 
 const char *evsel__group_name(struct evsel *evsel)
@@ -2092,7 +2240,7 @@ static void evsel__exit(struct evsel *evsel)
 	zfree(&evsel->filter);
 	zfree(&evsel->group_pmu_name);
 	zfree(&evsel->unit);
-	zfree(&evsel->metric_id);
+	evsel__free_metric_ids(evsel);
 	evsel__zero_per_pkg(evsel);
 	hashmap__free(evsel->per_pkg_mask);
 	evsel->per_pkg_mask = NULL;
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 53f3641b66e1..e776ca60a9a5 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -74,7 +74,7 @@ struct evsel {
 		double			scale;
 		const char		*unit;
 		struct cgroup		*cgrp;
-		const char		*metric_id;
+		char			**metric_ids;
 
 		/* The PMU the event is from. Used for missing_features, PMU name, etc. */
 		struct perf_pmu		*pmu;
@@ -336,7 +336,13 @@ int arch_evsel__hw_name(struct evsel *evsel, char *bf, size_t size);
 int __evsel__hw_cache_type_op_res_name(u8 type, u8 op, u8 result, char *bf, size_t size);
 const char *evsel__name(struct evsel *evsel);
 bool evsel__name_is(struct evsel *evsel, const char *name);
-const char *evsel__metric_id(const struct evsel *evsel);
+int evsel__for_each_metric_id(const struct evsel *evsel,
+			      int (*cb)(const char *metric_id, void *data),
+			      void *data);
+bool evsel__has_metric_id(const struct evsel *evsel, const char *metric_id);
+bool evsel__shares_metric_id(const struct evsel *a, const struct evsel *b);
+int evsel__add_metric_id(struct evsel *evsel, const char *metric_id);
+int evsel__copy_metric_ids(struct evsel *dst, const struct evsel *src);
 
 static inline bool evsel__is_retire_lat(const struct evsel *evsel)
 {
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 08679392bf6d..f25709750c3e 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -275,12 +275,34 @@ static bool contains_metric_id(struct evsel **metric_events, int num_events,
 	int i;
 
 	for (i = 0; i < num_events; i++) {
-		if (!strcmp(evsel__metric_id(metric_events[i]), metric_id))
+		if (evsel__has_metric_id(metric_events[i], metric_id))
 			return true;
 	}
 	return false;
 }
 
+struct match_metric_id_args {
+	/* The events already matched to IDs of the metric. */
+	struct evsel **metric_events;
+	size_t num_events;
+	/* The metric IDs to match. */
+	struct hashmap *ids;
+	/* The newly matched ID. */
+	const char *matched_id;
+};
+
+static int match_metric_id_cb(const char *metric_id, void *data)
+{
+	struct match_metric_id_args *args = data;
+
+	if (contains_metric_id(args->metric_events, args->num_events, metric_id))
+		return 0;
+	if (!hashmap__find(args->ids, metric_id, NULL))
+		return 0;
+	args->matched_id = metric_id;
+	return 1;
+}
+
 /**
  * setup_metric_events - Find a group of events in metric_evlist that correspond
  *                       to the IDs from a parsed metric expression.
@@ -293,8 +315,8 @@ static int setup_metric_events(const char *pmu, struct hashmap *ids,
 			       struct evlist *metric_evlist,
 			       struct evsel ***out_metric_events)
 {
+	struct match_metric_id_args args = { .ids = ids, };
 	struct evsel **metric_events;
-	const char *metric_id;
 	struct evsel *ev;
 	size_t ids_size, matched_events, i;
 	struct perf_pmus *pmus = perf_env__pmus(evlist__env(metric_evlist) ?: host_env());
@@ -310,9 +332,8 @@ static int setup_metric_events(const char *pmu, struct hashmap *ids,
 		return -ENOMEM;
 
 	matched_events = 0;
+	args.metric_events = metric_events;
 	evlist__for_each_entry(metric_evlist, ev) {
-		struct expr_id_data *val_ptr;
-
 		/* Don't match events for the wrong hybrid PMU. */
 		if (!all_pmus && ev->pmu && evsel__is_hybrid(ev) &&
 		    strcmp(ev->pmu->name, pmu))
@@ -323,21 +344,19 @@ static int setup_metric_events(const char *pmu, struct hashmap *ids,
 		 * events per socket on skylakex. Only the first such
 		 * event is placed in metric_events.
 		 */
-		metric_id = evsel__metric_id(ev);
-		if (contains_metric_id(metric_events, matched_events, metric_id))
-			continue;
 		/*
 		 * Does this event belong to the parse context? For
 		 * combined or shared groups, this metric may not care
 		 * about this event.
 		 */
-		if (hashmap__find(ids, metric_id, &val_ptr)) {
-			pr_debug("Matched metric-id %s to %s\n", metric_id, evsel__name(ev));
+		args.num_events = matched_events;
+		if (evsel__for_each_metric_id(ev, match_metric_id_cb, &args)) {
+			pr_debug("Matched metric-id %s to %s\n",
+				 args.matched_id, evsel__name(ev));
 			metric_events[matched_events++] = ev;
-
-			if (matched_events >= ids_size)
-				break;
 		}
+		if (matched_events >= ids_size)
+			break;
 	}
 	if (matched_events < ids_size) {
 		free(metric_events);
@@ -359,9 +378,8 @@ static int setup_metric_events(const char *pmu, struct hashmap *ids,
 		 * of such events to be the event that appears in
 		 * metric_events.
 		 */
-		metric_id = evsel__metric_id(ev);
 		evlist__for_each_entry_continue(metric_evlist, ev) {
-			if (!strcmp(evsel__metric_id(ev), metric_id))
+			if (evsel__shares_metric_id(ev, metric_events[i]))
 				ev->metric_leader = metric_events[i];
 		}
 	}
@@ -469,13 +487,6 @@ static struct evsel *map_metric_evsel_to_evlist(struct evlist *data_evlist,
 			continue;
 		if (metric_evsel->core.attr.config != data_evsel->core.attr.config)
 			continue;
-		/* Skip if the data event has a metric_id that doesn't match. */
-		if (data_evsel->metric_id &&
-		    strcmp(evsel__metric_id(metric_evsel), evsel__metric_id(data_evsel))) {
-			pr_debug("Skipping matching evsel due to differing metric ids '%s' vs '%s'\n",
-				 evsel__metric_id(metric_evsel), evsel__metric_id(data_evsel));
-			continue;
-		}
 		return data_evsel;
 	}
 	return NULL;
@@ -516,9 +527,8 @@ static int find_matching_metrics_cb(const struct pmu_metric *pm,
 		struct evsel *new_metric_leader;
 
 		if (!data_evsel) {
-			pr_debug("Skipping metric '%s' as evsel '%s' / '%s' is missing\n",
-				pm->metric_name, evsel__name(metric_evsel),
-				evsel__metric_id(metric_evsel));
+			pr_debug("Skipping metric '%s' as evsel '%s' is missing\n",
+				pm->metric_name, evsel__name(metric_evsel));
 			goto out;
 		}
 
@@ -535,8 +545,8 @@ static int find_matching_metrics_cb(const struct pmu_metric *pm,
 		/* Mismatching evsel leaders. */
 		if (data_evsel->metric_leader != new_metric_leader) {
 			pr_debug("Skipping metric '%s' due to mismatching evsel metric leaders '%s' vs '%s'\n",
-				pm->metric_name, evsel__metric_id(metric_evsel),
-				evsel__metric_id(data_evsel));
+				pm->metric_name, evsel__name(metric_evsel),
+				evsel__name(data_evsel));
 			goto out;
 		}
 	}
@@ -552,10 +562,8 @@ static int find_matching_metrics_cb(const struct pmu_metric *pm,
 					    metric_evsel,
 					    /*create=*/false);
 
-		if (data_evsel->metric_id == NULL) {
-			data_evsel->metric_id = metric_evsel->metric_id;
-			metric_evsel->metric_id = NULL;
-		}
+		if (evsel__copy_metric_ids(data_evsel, metric_evsel))
+			goto out;
 
 		if (data_evsel->metric_leader == NULL) {
 			if (metric_evsel->metric_leader == metric_evsel) {
@@ -610,6 +618,17 @@ int metricgroup__add_matching_metrics(struct evlist *evlist, bool system_wide)
 					    find_matching_metrics_cb, &args);
 }
 
+static int append_metric_id_cb(const char *metric_id, void *data)
+{
+	struct strbuf *sb = data;
+
+	/*
+	 * Stop after the first, the metric-id of a parsed metric event is its
+	 * first identifier.
+	 */
+	return strbuf_addstr(sb, metric_id) ?: 1;
+}
+
 /*
  * The metric events are parsed with a metric-id holding the identifier used in
  * the metric expression. If the evsel name is just the parsed event, which
@@ -626,16 +645,19 @@ static int set_metric_event_names(struct evlist *perf_evlist, const char *modifi
 	evlist__for_each_entry(perf_evlist, ev) {
 		bool has_slash = false;
 
-		if (!ev->metric_id || !strstr(ev->name, "metric-id="))
+		if (!strstr(ev->name, "metric-id="))
 			continue;
 
 		ret = strbuf_setlen(&sb, 0);
 		if (ret)
 			break;
 
-		ret = strbuf_addstr(&sb, ev->metric_id);
-		if (ret)
+		ret = evsel__for_each_metric_id(ev, append_metric_id_cb, &sb);
+		if (ret < 0)
 			break;
+		if (ret == 0)
+			continue;
+		ret = 0;
 
 		zfree(&ev->name);
 		for (cur = strchr(sb.buf, '@') ; cur; cur = strchr(++cur, '@')) {
@@ -1471,15 +1493,21 @@ static int parse_ids(bool metric_no_merge, bool fake_pmu,
 	return ret;
 }
 
+static int pctx_has_metric_id_cb(const char *metric_id, void *data)
+{
+	struct hashmap *ids = data;
+
+	return hashmap__find(ids, metric_id, NULL) ? 1 : 0;
+}
+
 /* How many times will a given evsel be used in a set of metrics? */
 static int count_uses(struct list_head *metric_list, struct evsel *evsel)
 {
-	const char *metric_id = evsel__metric_id(evsel);
 	struct metric *m;
 	int uses = 0;
 
 	list_for_each_entry(m, metric_list, nd) {
-		if (hashmap__find(m->pctx->ids, metric_id, NULL))
+		if (evsel__for_each_metric_id(evsel, pctx_has_metric_id_cb, m->pctx->ids))
 			uses++;
 	}
 	return uses;
diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c
index a640344afcc8..f50722e68b73 100644
--- a/tools/perf/util/parse-events.c
+++ b/tools/perf/util/parse-events.c
@@ -332,9 +332,9 @@ __add_event(struct list_head *list, int *idx,
 		int ret = parse_events__decode_metric_id(&sb, metric_id);
 
 		if (!ret)
-			evsel->metric_id = strdup(sb.buf);
+			ret = evsel__add_metric_id(evsel, sb.buf);
 		strbuf_release(&sb);
-		if (!evsel->metric_id)
+		if (ret)
 			goto out_err;
 	}
 
@@ -362,9 +362,7 @@ __add_event(struct list_head *list, int *idx,
 out_err:
 	perf_cpu_map__put(cpus);
 	perf_cpu_map__put(pmu_cpus);
-	zfree(&evsel->name);
-	zfree(&evsel->metric_id);
-	free(evsel);
+	evsel__put(evsel);
 	return NULL;
 }
 
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index a41372cd6bcb..79a4e9f02cbd 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -3073,6 +3073,27 @@ static PyObject *pyrf_evlist__metrics(struct pyrf_evlist *pevlist)
 	return list;
 }
 
+struct add_metric_id_val_args {
+	struct expr_parse_ctx *pctx;
+	double val;
+	int source_count;
+};
+
+static int add_metric_id_val_cb(const char *metric_id, void *data)
+{
+	struct add_metric_id_val_args *args = data;
+	char *n = strdup(metric_id);
+	int ret;
+
+	if (!n)
+		return -ENOMEM;
+
+	ret = expr__add_id_val_source_count(args->pctx, n, args->val, args->source_count);
+	if (ret)
+		free(n);
+	return ret;
+}
+
 static int prepare_metric(const struct metric_expr *mexp,
 			  const struct evsel *evsel,
 			  struct expr_parse_ctx *pctx,
@@ -3080,6 +3101,7 @@ static int prepare_metric(const struct metric_expr *mexp,
 {
 	struct evsel * const *metric_events = mexp->metric_events;
 	struct metric_ref *metric_refs = mexp->metric_refs;
+	struct add_metric_id_val_args args = { .pctx = pctx, };
 
 	for (int i = 0; metric_events[i]; i++) {
 		struct evsel *cur = metric_events[i];
@@ -3127,15 +3149,11 @@ static int prepare_metric(const struct metric_expr *mexp,
 
 		if (ena != run && run != 0)
 			val = val * ena / run;
-		char *n = strdup(evsel__metric_id(cur));
-
-		if (!n)
-			return -ENOMEM;
-		ret = expr__add_id_val_source_count(pctx, n, val, source_count);
-		if (ret) {
-			free(n);
+		args.val = val;
+		args.source_count = source_count;
+		ret = evsel__for_each_metric_id(cur, add_metric_id_val_cb, &args);
+		if (ret)
 			return ret;
-		}
 	}
 
 	for (int i = 0; metric_refs && metric_refs[i].metric_name; i++) {
diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
index f5ae399bf336..587db3e2a05b 100644
--- a/tools/perf/util/stat-shadow.c
+++ b/tools/perf/util/stat-shadow.c
@@ -45,6 +45,29 @@ static bool tool_pmu__is_time_event(const struct perf_stat_config *config,
 	return true;
 }
 
+struct add_metric_id_val_args {
+	struct expr_parse_ctx *pctx;
+	double val;
+	int source_count;
+	int aggr_nr;
+};
+
+static int add_metric_id_val_cb(const char *metric_id, void *data)
+{
+	struct add_metric_id_val_args *args = data;
+	char *n = strdup(metric_id);
+	int ret;
+
+	if (!n)
+		return -ENOMEM;
+
+	ret = expr__add_id_val_source_count_aggr_nr(args->pctx, n, args->val,
+						    args->source_count, args->aggr_nr);
+	if (ret)
+		free(n);
+	return ret;
+}
+
 static int prepare_metric(struct perf_stat_config *config,
 			  const struct metric_expr *mexp,
 			  const struct evsel *evsel,
@@ -53,15 +76,16 @@ static int prepare_metric(struct perf_stat_config *config,
 {
 	struct evsel * const *metric_events = mexp->metric_events;
 	struct metric_ref *metric_refs = mexp->metric_refs;
+	struct add_metric_id_val_args args = { .pctx = pctx, };
 	int i;
 
 	for (i = 0; metric_events[i]; i++) {
 		int source_count = 0, tool_aggr_idx;
 		int aggr_nr = 1;
+		int ret;
 		bool is_tool_time =
 			tool_pmu__is_time_event(config, metric_events[i], &tool_aggr_idx);
 		struct perf_stat_evsel *ps = metric_events[i]->stats;
-		char *n;
 		double val;
 
 		/*
@@ -114,11 +138,12 @@ static int prepare_metric(struct perf_stat_config *config,
 				aggr_nr = aggr->nr ?: 1;
 			}
 		}
-		n = strdup(evsel__metric_id(metric_events[i]));
-		if (!n)
-			return -ENOMEM;
-
-		expr__add_id_val_source_count_aggr_nr(pctx, n, val, source_count, aggr_nr);
+		args.val = val;
+		args.source_count = source_count;
+		args.aggr_nr = aggr_nr;
+		ret = evsel__for_each_metric_id(metric_events[i], add_metric_id_val_cb, &args);
+		if (ret)
+			return ret;
 	}
 
 	for (int j = 0; metric_refs && metric_refs[j].metric_name; j++) {
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 22/30] perf pmu: Add perf_pmu__for_each_name_from_config()
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (20 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 21/30] perf evsel: Generalize metric_id to a set of metric_ids Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:13   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 23/30] perf metricgroup: Share metric events by their counter encoding Ian Rogers
                   ` (7 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

perf_pmu__name_from_config() returns the first event alias of a PMU
whose parsed config matches, ignoring the perf_event_attr type. So a
legacy hardware event alias can be returned for a raw config of the
same value, only one of possibly several names is found, and the names
of legacy events whose aliases were replaced by sysfs or json events of
the same name can't be found.

Add perf_pmu__for_each_name_from_config() that calls a callback for
each event name of a PMU whose encoding has a given type and config,
ignoring the extended PMU type of legacy encodings and, for core PMUs,
also searching the legacy hardware and cache event tables. Make
perf_pmu__name_from_config() return the first such name for the PMU's
type.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 105 +++++++++++++++++++++++++++++++++++++++---
 tools/perf/util/pmu.h |   3 ++
 2 files changed, 101 insertions(+), 7 deletions(-)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index d5384c074265..381189acfd4a 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -2812,25 +2812,116 @@ void perf_pmu__delete(struct perf_pmu *pmu)
 	free(pmu);
 }
 
-const char *perf_pmu__name_from_config(struct perf_pmu *pmu, u64 config)
+static bool pmu_config_equal(__u32 type, u64 a, u64 b)
+{
+	/* Ignore the extended PMU type in the upper bits of legacy encodings. */
+	if (type == PERF_TYPE_HARDWARE || type == PERF_TYPE_HW_CACHE)
+		return (a & PERF_HW_EVENT_MASK) == (b & PERF_HW_EVENT_MASK);
+	return a == b;
+}
+
+struct legacy_name_args {
+	__u32 type;
+	u64 config;
+	int (*cb)(const char *name, void *data);
+	void *data;
+};
+
+static int pmu_legacy_name_cb(const struct pmu_event *pe,
+			      const struct pmu_events_table *table __maybe_unused,
+			      void *data)
+{
+	static const char hw_prefix[] = "legacy-hardware-config=";
+	static const char cache_prefix[] = "legacy-cache-config=";
+	struct legacy_name_args *args = data;
+	const char *prefix = args->type == PERF_TYPE_HARDWARE ? hw_prefix : cache_prefix;
+
+	if (!pe->name || !pe->event || !strstarts(pe->event, prefix))
+		return 0;
+	if (!pmu_config_equal(args->type, strtoull(pe->event + strlen(prefix), NULL, 0),
+			      args->config))
+		return 0;
+	return args->cb(pe->name, args->data);
+}
+
+/**
+ * perf_pmu__for_each_name_from_config - Call @cb for event names of an encoding.
+ * @pmu: The PMU whose event names are searched.
+ * @type: The perf_event_attr type of the encoding, such as the PMU's type or
+ *        PERF_TYPE_HARDWARE for a legacy hardware event.
+ * @config: The perf_event_attr config of the encoding.
+ * @cb: Called with each event name of @pmu with the encoding, a non-zero return
+ *      stops the iteration.
+ * @data: Passed to @cb.
+ *
+ * As event aliases from sysfs or json replace legacy event aliases of the same
+ * name, the names of legacy hardware and cache events of core PMUs are found
+ * from the legacy event tables. @cb may be called more than once for a name.
+ *
+ * Return: 0 or the first non-zero value returned by @cb.
+ */
+int perf_pmu__for_each_name_from_config(struct perf_pmu *pmu, __u32 type, u64 config,
+					int (*cb)(const char *name, void *data),
+					void *data)
 {
 	struct hashmap_entry *entry;
 	size_t bkt;
+	int ret;
 
 	if (!pmu)
-		return NULL;
+		return 0;
 
 	pmu_aliases_parse(pmu);
 	pmu_add_cpu_aliases(pmu);
 	hashmap__for_each_entry(pmu->aliases, entry, bkt) {
 		struct perf_pmu_alias *event = entry->pvalue;
-		struct perf_event_attr attr = {.config = 0,};
-		int ret = perf_pmu__parse_terms_to_attr(pmu, event->terms, &attr);
+		struct perf_event_attr attr = { .type = pmu->type, };
 
-		if (ret == 0 && config == attr.config)
-			return event->name;
+		if (perf_pmu__parse_terms_to_attr(pmu, event->terms, &attr) ||
+		    attr.type != type || !pmu_config_equal(type, attr.config, config))
+			continue;
+		ret = cb(event->name, data);
+		if (ret)
+			return ret;
 	}
-	return NULL;
+	if (pmu->is_core && (type == PERF_TYPE_HARDWARE || type == PERF_TYPE_HW_CACHE)) {
+		struct legacy_name_args args = {
+			.type = type,
+			.config = config,
+			.cb = cb,
+			.data = data,
+		};
+
+		return pmu_events_table__for_each_event(perf_pmu__default_core_events_table(),
+							/*pmu=*/NULL, pmu_legacy_name_cb, &args);
+	}
+	return 0;
+}
+
+static int first_name_cb(const char *name, void *data)
+{
+	const char **first = data;
+
+	*first = name;
+	return 1;
+}
+
+/**
+ * perf_pmu__name_from_config - The first event name of @pmu with @config.
+ * @pmu: The PMU whose event names are searched.
+ * @config: The perf_event_attr config of an event of @pmu's type.
+ *
+ * Return: An event name or NULL if none was found.
+ */
+const char *perf_pmu__name_from_config(struct perf_pmu *pmu, u64 config)
+{
+	const char *name = NULL;
+
+	if (!pmu)
+		return NULL;
+
+	perf_pmu__for_each_name_from_config(pmu, pmu->type, config, first_name_cb, &name);
+	return name;
 }
 
 bool perf_pmu__reads_only_on_cpu_idx0(const struct perf_event_attr *attr)
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 7605c3859337..ab7123964423 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -365,6 +365,9 @@ struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char
 struct perf_pmu *perf_pmu__create_placeholder_core_pmu(struct list_head *core_pmus);
 void perf_pmu__delete(struct perf_pmu *pmu);
 
+int perf_pmu__for_each_name_from_config(struct perf_pmu *pmu, __u32 type, u64 config,
+					int (*cb)(const char *name, void *data),
+					void *data);
 const char *perf_pmu__name_from_config(struct perf_pmu *pmu, u64 config);
 bool perf_pmu__is_fake(const struct perf_pmu *pmu);
 
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 23/30] perf metricgroup: Share metric events by their counter encoding
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (21 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 22/30] perf pmu: Add perf_pmu__for_each_name_from_config() Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:59   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 24/30] perf stat: Share events between metrics and command line events Ian Rogers
                   ` (6 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Metrics share events to reduce multiplexing. Events were shared by the
names of the identifiers in the metric expressions: the identifiers of
ungrouped metrics were combined into one set of events, and a grouped
metric reused the events of an earlier metric whose identifiers were a
superset of its own. Events spelled differently but counting the same
thing, such as "slots" and "cpu_core/TOPDOWN.SLOTS/" or "cycles" and
"cpu-cycles", weren't shared, and events were only shared within the
metrics of one metricgroup__parse_groups() call.

Add an evsel matcher that compares a key of the counter an evsel
programs: the PMU name, the perf_event_attr type and config fields, the
exclusion bits and other modifiers, and the cgroup. Legacy hardware
events of core PMUs are compared using the encoding of the equivalent
sysfs or json event. Parse the events of each metric and then move them
to the evlist, unless the evlist already has an event counting the same
thing with compatible metric leadership, such as from an earlier metric,
an earlier metricgroup__parse_groups() call or the command line, in
which case that event is used. Events of a grouped metric are only
shared when all of them are found in one group; when grouping was only
forced by the architecture (such as x86 topdown slots and perf-metrics
events), new members are appended to an existing forced group. Ungrouped
events only match ungrouped events so that a metric's events aren't
split across multiplexing groups. The metric ids of a metric's events
are added to the shared events, and the metric's events refer to the
shared events. --metric-no-merge disables the sharing.

Remove build_combined_expr_ctx(), find_tool_events(), which added every
tool event to every metric so that the identifier sets could be
supersets, and expr__subset_of_ids(), now unused.

For example, on an Alder Lake system "--cputype=core -e
'{cpu_core/instructions/,cpu_core/cpu-cycles/}' -M tma_info_thread_ipc"
now shares that 2-event group on cpu_core rather than also opening
"{INST_RETIRED.ANY,CPU_CLK_UNHALTED.THREAD}".

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/expr.c        |   15 -
 tools/perf/util/expr.h        |    2 -
 tools/perf/util/metricgroup.c | 1097 +++++++++++++++++++++++++++------
 3 files changed, 897 insertions(+), 217 deletions(-)

diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index 09ced31ef59e..09dad66e3835 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -235,21 +235,6 @@ int expr__get_id(struct expr_parse_ctx *ctx, const char *id,
 	return hashmap__find(ctx->ids, id, data) ? 0 : -1;
 }
 
-bool expr__subset_of_ids(struct expr_parse_ctx *haystack,
-			 struct expr_parse_ctx *needles)
-{
-	struct hashmap_entry *cur;
-	size_t bkt;
-	struct expr_id_data *data;
-
-	hashmap__for_each_entry(needles->ids, cur, bkt) {
-		if (expr__get_id(haystack, cur->pkey, &data))
-			return false;
-	}
-	return true;
-}
-
-
 int expr__resolve_id(struct expr_parse_ctx *ctx, const char *id,
 		     struct expr_id_data **datap)
 {
diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
index ed12e4007d2d..7e22a1ea6ce1 100644
--- a/tools/perf/util/expr.h
+++ b/tools/perf/util/expr.h
@@ -42,8 +42,6 @@ int expr__add_id_val_source_count_aggr_nr(struct expr_parse_ctx *ctx, const char
 int expr__add_ref(struct expr_parse_ctx *ctx, struct metric_ref *ref);
 int expr__get_id(struct expr_parse_ctx *ctx, const char *id,
 		 struct expr_id_data **data);
-bool expr__subset_of_ids(struct expr_parse_ctx *haystack,
-			 struct expr_parse_ctx *needles);
 int expr__resolve_id(struct expr_parse_ctx *ctx, const char *id,
 		     struct expr_id_data **datap);
 
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index f25709750c3e..f4a2b280091c 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -155,11 +155,15 @@ struct metric {
 	/** Show events even if in the Default metric group. */
 	bool default_show_events;
 	/**
-	 * Parsed events for the metric. Optional as events may be taken from a
-	 * different metric whose group contains all the IDs necessary for this
-	 * one.
+	 * Events parsed for the metric before they are moved to, or replaced by
+	 * equivalent events in, the evlist the metrics are added to.
 	 */
 	struct evlist *evlist;
+	/**
+	 * The NULL terminated events of the metric's expression, placed in a
+	 * metric_expr.
+	 */
+	struct evsel **metric_events;
 };
 
 static void metric__watchdog_constraint_hint(const char *name, bool foot)
@@ -215,6 +219,7 @@ static void metric__free(struct metric *m)
 	expr__ctx_free(m->pctx);
 	zfree(&m->modifier);
 	evlist__put(m->evlist);
+	free(m->metric_events);
 	free(m);
 }
 
@@ -379,6 +384,9 @@ static int setup_metric_events(const char *pmu, struct hashmap *ids,
 		 * metric_events.
 		 */
 		evlist__for_each_entry_continue(metric_evlist, ev) {
+			if (!all_pmus && ev->pmu && evsel__is_hybrid(ev) &&
+			    strcmp(ev->pmu->name, pmu))
+				continue;
 			if (evsel__shares_metric_id(ev, metric_events[i]))
 				ev->metric_leader = metric_events[i];
 		}
@@ -471,6 +479,512 @@ int metricgroup__for_each_metric(struct perf_pmus *pmus, const struct pmu_metric
 	return pmu_for_each_sys_metric(metricgroup__sys_event_iter, &sys_data);
 }
 
+/*
+ * A key identifying the counter an evsel programs, used to find evsels counting
+ * the same thing, such as when metrics share events.
+ */
+struct counter_key {
+	/** @pmu_name: Name of the evsel's PMU, or NULL if unknown. */
+	const char *pmu_name;
+	/** @cgrp: The cgroup the evsel counts. */
+	const struct cgroup *cgrp;
+	/** @type: perf_event_attr type, PERF_TYPE_RAW for any dynamic PMU type. */
+	__u32 type;
+	/** @config: perf_event_attr config without legacy extended type bits. */
+	__u64 config;
+	/** @config1: perf_event_attr config1. */
+	__u64 config1;
+	/** @config2: perf_event_attr config2. */
+	__u64 config2;
+	/** @config3: perf_event_attr config3. */
+	__u64 config3;
+	/** @config4: perf_event_attr config4. */
+	__u64 config4;
+	/** @exclude: perf_event_attr exclude bits, a mask of COUNTER_KEY_EXCLUDE_*. */
+	unsigned int exclude;
+	/**
+	 * @modifiers: Other perf_event_attr and evsel modifiers affecting what
+	 * is counted, a mask of COUNTER_KEY_MOD_*.
+	 */
+	unsigned int modifiers;
+	/** @precise_ip: perf_event_attr precise_ip. */
+	unsigned int precise_ip;
+};
+
+#define COUNTER_KEY_EXCLUDE_USER	BIT(0)
+#define COUNTER_KEY_EXCLUDE_KERNEL	BIT(1)
+#define COUNTER_KEY_EXCLUDE_HV		BIT(2)
+#define COUNTER_KEY_EXCLUDE_HOST	BIT(3)
+#define COUNTER_KEY_EXCLUDE_GUEST	BIT(4)
+#define COUNTER_KEY_EXCLUDE_IDLE	BIT(5)
+
+#define COUNTER_KEY_MOD_PINNED		BIT(0)
+#define COUNTER_KEY_MOD_EXCLUSIVE	BIT(1)
+#define COUNTER_KEY_MOD_PERCORE		BIT(2)
+#define COUNTER_KEY_MOD_RETIRE_LAT	BIT(3)
+#define COUNTER_KEY_MOD_PRECISE_MAX	BIT(4)
+#define COUNTER_KEY_MOD_BPF		BIT(5)
+
+/* Cached sysfs encodings of legacy hardware events on a PMU. */
+struct legacy_hw_key {
+	struct list_head list;
+	char *pmu_name;
+	__u64 hw_config;
+	struct counter_key key;
+};
+
+struct evsel_matcher_entry {
+	struct evsel *evsel;
+	struct counter_key key;
+};
+
+/**
+ * struct evsel_matcher - Find the evsel, in a set of evsels, counting the same
+ * thing as another evsel.
+ */
+struct evsel_matcher {
+	/** @entries: The evsels that may be matched and their counter keys. */
+	struct evsel_matcher_entry *entries;
+	/** @nr_entries: Number of entries in use. */
+	size_t nr_entries;
+	/** @max_entries: Number of entries allocated. */
+	size_t max_entries;
+	/** @legacy_hw_keys: Cache of legacy_hw_key. */
+	struct list_head legacy_hw_keys;
+};
+
+static void counter_key__init_attr(struct counter_key *key, const char *pmu_name,
+				   const struct perf_event_attr *attr)
+{
+	memset(key, 0, sizeof(*key));
+	key->pmu_name = pmu_name;
+	key->type = attr->type;
+	key->config = attr->config;
+	key->config1 = attr->config1;
+	key->config2 = attr->config2;
+	key->config3 = attr->config3;
+	key->config4 = attr->config4;
+	key->exclude = (attr->exclude_user ? COUNTER_KEY_EXCLUDE_USER : 0) |
+		       (attr->exclude_kernel ? COUNTER_KEY_EXCLUDE_KERNEL : 0) |
+		       (attr->exclude_hv ? COUNTER_KEY_EXCLUDE_HV : 0) |
+		       (attr->exclude_host ? COUNTER_KEY_EXCLUDE_HOST : 0) |
+		       (attr->exclude_guest ? COUNTER_KEY_EXCLUDE_GUEST : 0) |
+		       (attr->exclude_idle ? COUNTER_KEY_EXCLUDE_IDLE : 0);
+	key->modifiers = (attr->pinned ? COUNTER_KEY_MOD_PINNED : 0) |
+			 (attr->exclusive ? COUNTER_KEY_MOD_EXCLUSIVE : 0);
+	key->precise_ip = attr->precise_ip;
+	if (attr->type == PERF_TYPE_HARDWARE || attr->type == PERF_TYPE_HW_CACHE) {
+		key->config &= PERF_HW_EVENT_MASK;
+	} else if (attr->type >= PERF_TYPE_MAX && pmu_name) {
+		/*
+		 * Dynamic PMU type numbers may differ between machines, compare
+		 * the PMU name instead. Without a PMU name keep the type so
+		 * that the event doesn't match the same config on any PMU.
+		 */
+		key->type = PERF_TYPE_RAW;
+	}
+}
+
+/* Copy the counter encoding, but not the PMU name or modifiers, of @src. */
+static void counter_key__set_counter(struct counter_key *key, const struct counter_key *src)
+{
+	key->type = src->type;
+	key->config = src->config;
+	key->config1 = src->config1;
+	key->config2 = src->config2;
+	key->config3 = src->config3;
+	key->config4 = src->config4;
+}
+
+/*
+ * Parse "<pmu_name>/<name>/" and, if the encoding isn't a legacy hardware
+ * event, such as because sysfs or json provide an encoding that is preferred
+ * by the perf tool, set @key to the encoding.
+ */
+static bool core_event_key(const char *pmu_name, const char *name, struct counter_key *key)
+{
+	struct parse_events_error err;
+	struct evlist *evlist;
+	struct evsel *evsel;
+	char *str;
+	bool found = false;
+
+	if (asprintf(&str, "%s/%s/", pmu_name, name) < 0)
+		return false;
+
+	evlist = evlist__new();
+	if (!evlist) {
+		free(str);
+		return false;
+	}
+	parse_events_error__init(&err);
+	if (!parse_events(evlist, str, &err) && evlist__nr_entries(evlist) == 1) {
+		evsel = evlist__first(evlist);
+		if (evsel->core.attr.type != PERF_TYPE_HARDWARE) {
+			counter_key__init_attr(key, pmu_name, &evsel->core.attr);
+			found = true;
+		}
+	}
+	parse_events_error__exit(&err);
+	evlist__put(evlist);
+	free(str);
+	return found;
+}
+
+struct legacy_hw_name_args {
+	const char *pmu_name;
+	struct counter_key *key;
+};
+
+/* Is there a non-legacy encoding of the legacy hardware event @name? */
+static int legacy_hw_name_cb(const char *name, void *data)
+{
+	struct legacy_hw_name_args *args = data;
+
+	return core_event_key(args->pmu_name, name, args->key) ? 1 : 0;
+}
+
+/*
+ * Legacy hardware events, such as "cycles" in "perf record -e cycles", are
+ * commonly also available as a sysfs or json event of the core PMU, such as
+ * "cpu_core/cpu-cycles/", whose encoding is preferred by the perf tool and is
+ * the encoding metrics use. Find the names of the legacy hardware event and
+ * compute the key of the preferred encoding using the live host PMU of the same
+ * name so that the two encodings match.
+ */
+static bool legacy_hw_key(struct evsel_matcher *matcher, const char *pmu_name,
+			  __u64 hw_config, struct counter_key *key)
+{
+	struct legacy_hw_key *pos;
+	struct legacy_hw_name_args name_args;
+	struct perf_pmu *pmu;
+
+	list_for_each_entry(pos, &matcher->legacy_hw_keys, list) {
+		if (pos->hw_config == hw_config && !strcmp(pos->pmu_name, pmu_name)) {
+			if (pos->key.type == PERF_TYPE_HARDWARE)
+				return false;
+			counter_key__set_counter(key, &pos->key);
+			return true;
+		}
+	}
+	pos = zalloc(sizeof(*pos));
+	if (!pos)
+		return false;
+	pos->pmu_name = strdup(pmu_name);
+	if (!pos->pmu_name) {
+		free(pos);
+		return false;
+	}
+	pos->hw_config = hw_config;
+	pos->key.type = PERF_TYPE_HARDWARE;
+	list_add(&pos->list, &matcher->legacy_hw_keys);
+
+	pmu = perf_pmus__find(perf_env__pmus(host_env()), pmu_name);
+	if (!pmu || !pmu->is_core)
+		return false;
+
+	name_args.pmu_name = pos->pmu_name;
+	name_args.key = &pos->key;
+	if (perf_pmu__for_each_name_from_config(pmu, PERF_TYPE_HARDWARE, hw_config,
+						legacy_hw_name_cb, &name_args) != 1) {
+		pos->key.type = PERF_TYPE_HARDWARE;
+		return false;
+	}
+	pos->key.pmu_name = pos->pmu_name;
+	counter_key__set_counter(key, &pos->key);
+	return true;
+}
+
+static void evsel_matcher__key(struct evsel_matcher *matcher, struct evsel *evsel,
+			       struct counter_key *key)
+{
+	struct perf_pmu *pmu = evsel__find_pmu(evsel);
+
+	counter_key__init_attr(key, pmu ? pmu->name : NULL, &evsel->core.attr);
+	key->cgrp = evsel->cgrp;
+	if (evsel->percore)
+		key->modifiers |= COUNTER_KEY_MOD_PERCORE;
+	if (evsel__is_retire_lat(evsel))
+		key->modifiers |= COUNTER_KEY_MOD_RETIRE_LAT;
+	if (evsel->precise_max)
+		key->modifiers |= COUNTER_KEY_MOD_PRECISE_MAX;
+	if (evsel->bpf_counter)
+		key->modifiers |= COUNTER_KEY_MOD_BPF;
+	if (key->type == PERF_TYPE_HARDWARE && pmu && pmu->is_core)
+		legacy_hw_key(matcher, pmu->name, key->config, key);
+}
+
+/* Do @a and @b count the same thing? */
+static bool counter_key__equal(const struct counter_key *a, const struct counter_key *b)
+{
+	if (a->type != b->type || a->config != b->config ||
+	    a->config1 != b->config1 || a->config2 != b->config2 ||
+	    a->config3 != b->config3 || a->config4 != b->config4)
+		return false;
+
+	if ((a->type == PERF_TYPE_RAW || a->type >= PERF_TYPE_MAX) &&
+	    !a->pmu_name != !b->pmu_name)
+		return false;
+
+	if (a->pmu_name && b->pmu_name && strcmp(a->pmu_name, b->pmu_name))
+		return false;
+
+	return a->exclude == b->exclude && a->modifiers == b->modifiers &&
+	       a->precise_ip == b->precise_ip && a->cgrp == b->cgrp;
+}
+
+static bool evsel__is_grouped(const struct evsel *evsel)
+{
+	return evsel__leader(evsel)->core.nr_members > 1 ||
+	       (evsel->core.attr.read_format & PERF_FORMAT_GROUP);
+}
+
+/**
+ * evsel_matcher__add() - Add @evsel to the evsels that may be matched.
+ * @matcher: The matcher.
+ * @evsel: The evsel to add.
+ */
+static int evsel_matcher__add(struct evsel_matcher *matcher, struct evsel *evsel)
+{
+	struct evsel_matcher_entry *entry;
+
+	if (matcher->nr_entries == matcher->max_entries) {
+		size_t max_entries = matcher->max_entries ? matcher->max_entries * 2 : 16;
+		struct evsel_matcher_entry *entries =
+			realloc(matcher->entries, max_entries * sizeof(*entries));
+
+		if (!entries)
+			return -ENOMEM;
+		matcher->entries = entries;
+		matcher->max_entries = max_entries;
+	}
+	entry = &matcher->entries[matcher->nr_entries++];
+	entry->evsel = evsel;
+	evsel_matcher__key(matcher, evsel, &entry->key);
+	return 0;
+}
+
+/**
+ * evsel_matcher__init() - Initialize a matcher of the evsels in @evlist.
+ * @matcher: The matcher to initialize.
+ * @evlist: The evsels that may be matched, more may be added with
+ *          evsel_matcher__add().
+ */
+static int evsel_matcher__init(struct evsel_matcher *matcher, struct evlist *evlist)
+{
+	struct evsel *evsel;
+
+	memset(matcher, 0, sizeof(*matcher));
+	INIT_LIST_HEAD(&matcher->legacy_hw_keys);
+	evlist__for_each_entry(evlist, evsel) {
+		int ret = evsel_matcher__add(matcher, evsel);
+
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static void evsel_matcher__exit(struct evsel_matcher *matcher)
+{
+	struct legacy_hw_key *pos, *tmp;
+
+	list_for_each_entry_safe(pos, tmp, &matcher->legacy_hw_keys, list) {
+		list_del(&pos->list);
+		free(pos->pmu_name);
+		free(pos);
+	}
+	zfree(&matcher->entries);
+	matcher->nr_entries = matcher->max_entries = 0;
+}
+
+static bool evsel__is_forced_group(struct evsel *leader)
+{
+	struct evsel *pos;
+
+	if (leader->dont_regroup)
+		return false;
+
+	for_each_group_evsel(pos, leader) {
+		if (!arch_evsel__must_be_in_group(pos))
+			return false;
+	}
+	return true;
+}
+
+/*
+ * Does @pos have a metric_leader compatible with @evsel when @evsel's metric
+ * leader has @metric_sources events and was mapped to @mapped_metric_leader?
+ */
+static bool metric_leader_compatible(const struct evsel *evsel, int metric_sources,
+				     const struct evsel *mapped_metric_leader,
+				     const struct evsel *pos)
+{
+	if (!evsel->metric_leader || metric_sources == 0)
+		return true;
+
+	if (evsel->metric_leader == evsel) {
+		if (!pos->metric_leader)
+			return true;
+		return pos->metric_leader == pos && evsel__source_count(pos) == metric_sources;
+	}
+
+	if (!mapped_metric_leader)
+		return false;
+	if (!mapped_metric_leader->metric_leader)
+		return !pos->metric_leader && pos != mapped_metric_leader;
+	return pos->metric_leader == mapped_metric_leader;
+}
+
+static void own_evsel__metric_match_info(struct evsel **own, struct evsel **map, int nr,
+					 const struct evsel *ev, int *metric_sources,
+					 const struct evsel **mapped_metric_leader)
+{
+	int count = 0;
+
+	*metric_sources = 0;
+	*mapped_metric_leader = NULL;
+	if (!ev->metric_leader)
+		return;
+
+	for (int i = 0; i < nr; i++) {
+		if (own[i]->metric_leader == ev->metric_leader)
+			count++;
+		if (own[i] == ev->metric_leader)
+			*mapped_metric_leader = map[i];
+	}
+	*metric_sources = count;
+}
+
+/**
+ * evsel_matcher__find_for_metric() - Find the evsel counting the same thing as
+ *                                    @evsel with compatible metric leadership.
+ * @matcher: The matcher.
+ * @evsel: The evsel to match.
+ * @leader: If not NULL, the group leader of the matched evsel.
+ * @metric_sources: Number of events in @evsel's metric_leader family, or 0.
+ * @mapped_metric_leader: Replacement of @evsel->metric_leader when @evsel is
+ *                        not its own metric leader.
+ * @forced_group_leader: Require a group leader whose members all satisfy
+ *                       arch_evsel__must_be_in_group().
+ *
+ * The same counter may be present more than once, such as when the counter is
+ * in different groups. Prefer an evsel with the same name and the same group
+ * membership.
+ */
+static struct evsel *evsel_matcher__find_for_metric(struct evsel_matcher *matcher,
+						    struct evsel *evsel,
+						    const struct evsel *leader,
+						    int metric_sources,
+						    const struct evsel *mapped_metric_leader,
+						    bool forced_group_leader)
+{
+	struct evsel *best = NULL;
+	struct counter_key key;
+	bool grouped = evsel__is_grouped(evsel);
+	bool fake = evsel->pmu && perf_pmu__is_fake(evsel->pmu);
+	int best_score = -1;
+
+	evsel_matcher__key(matcher, evsel, &key);
+	for (size_t i = 0; i < matcher->nr_entries; i++) {
+		struct evsel_matcher_entry *entry = &matcher->entries[i];
+		struct evsel *pos = entry->evsel;
+		int score;
+
+		if (pos == evsel || (leader && evsel__leader(pos) != leader))
+			continue;
+
+		/*
+		 * Don't match an ungrouped event to a member of a group, as the
+		 * group may multiplex differently from the metric's other
+		 * events.
+		 */
+		if (!leader && !forced_group_leader && evsel__is_grouped(pos))
+			continue;
+
+		if (forced_group_leader &&
+		    (!evsel__is_group_leader(pos) || !evsel__is_forced_group(pos)))
+			continue;
+
+		/* Skip if the evsels don't program the same counter. */
+		if (!counter_key__equal(&key, &entry->key))
+			continue;
+
+		if (!metric_leader_compatible(evsel, metric_sources, mapped_metric_leader, pos))
+			continue;
+
+		/* The encoding of events on a fake PMU, used in testing, is meaningless. */
+		if ((fake || (pos->pmu && perf_pmu__is_fake(pos->pmu))) &&
+		    !evsel__shares_metric_id(evsel, pos))
+			continue;
+
+		score = (!strcmp(evsel__name(evsel), evsel__name(pos)) ? 2 : 0) +
+			(grouped == evsel__is_grouped(pos) ? 1 : 0);
+		if (score > best_score) {
+			best = pos;
+			best_score = score;
+		}
+	}
+	return best;
+}
+
+static __maybe_unused struct evsel *evsel_matcher__find(struct evsel_matcher *matcher,
+							struct evsel *evsel,
+							const struct evsel *leader)
+{
+	return evsel_matcher__find_for_metric(matcher, evsel, leader,
+					      /*metric_sources=*/0,
+					      /*mapped_metric_leader=*/NULL,
+					      /*forced_group_leader=*/false);
+}
+
+/**
+ * evsel_matcher__find_group() - Find a group of evsels counting the same thing
+ *                               as the members of the group led by @leader.
+ * @matcher: The matcher.
+ * @leader: The leader of the group to match.
+ * @own: Events of the metric being matched.
+ * @map: Matched replacements of @own.
+ * @nr: Number of events in @own.
+ * @out: Array, in the order of the group members, of the matched evsels.
+ */
+static bool evsel_matcher__find_group(struct evsel_matcher *matcher, struct evsel *leader,
+				      struct evsel **own, struct evsel **map, int nr,
+				      struct evsel **out)
+{
+	for (size_t i = 0; i < matcher->nr_entries; i++) {
+		struct evsel *pos_leader = matcher->entries[i].evsel;
+		struct evsel *member;
+		bool found = true;
+		int j = 0;
+
+		if (!evsel__is_group_leader(pos_leader) ||
+		    pos_leader->core.nr_members < leader->core.nr_members)
+			continue;
+
+		for_each_group_evsel(member, leader) {
+			int metric_sources;
+			const struct evsel *mapped_leader;
+
+			own_evsel__metric_match_info(own, map, nr, member,
+						     &metric_sources, &mapped_leader);
+			out[j] = evsel_matcher__find_for_metric(matcher, member, pos_leader,
+								metric_sources, mapped_leader,
+								/*forced_group_leader=*/false);
+			if (!out[j]) {
+				for (int k = 0; k < j; k++)
+					out[k] = NULL;
+				found = false;
+				break;
+			}
+			j++;
+		}
+		if (found)
+			return true;
+	}
+	return false;
+}
+
 struct find_matching_metrics_args {
 	struct evlist *evlist;
 	bool system_wide;
@@ -1335,89 +1849,20 @@ static void metricgroup__free_metrics(struct list_head *metric_list)
 	}
 }
 
-/**
- * find_tool_events - Search for the pressence of tool events in metric_list.
- * @metric_list: List to take metrics from.
- * @tool_events: Array of false values, indices corresponding to tool events set
- *               to true if tool event is found.
- */
-static void find_tool_events(const struct list_head *metric_list,
-			     bool tool_events[TOOL_PMU__EVENT_MAX])
-{
-	struct metric *m;
-
-	list_for_each_entry(m, metric_list, nd) {
-		int i;
-
-		tool_pmu__for_each_event(i) {
-			struct expr_id_data *data;
-
-			if (!tool_events[i] &&
-			    !expr__get_id(m->pctx, tool_pmu__event_to_str(i), &data))
-				tool_events[i] = true;
-		}
-	}
-}
-
-/**
- * build_combined_expr_ctx - Make an expr_parse_ctx with all !group_events
- *                           metric IDs, as the IDs are held in a set,
- *                           duplicates will be removed.
- * @metric_list: List to take metrics from.
- * @combined: Out argument for result.
- */
-static int build_combined_expr_ctx(const struct list_head *metric_list,
-				   struct expr_parse_ctx **combined)
-{
-	struct hashmap_entry *cur;
-	size_t bkt;
-	struct metric *m;
-	char *dup;
-	int ret;
-
-	*combined = expr__ctx_new();
-	if (!*combined)
-		return -ENOMEM;
-
-	list_for_each_entry(m, metric_list, nd) {
-		if (!m->group_events && !m->modifier) {
-			hashmap__for_each_entry(m->pctx->ids, cur, bkt) {
-				dup = strdup(cur->pkey);
-				if (!dup) {
-					ret = -ENOMEM;
-					goto err_out;
-				}
-				ret = expr__add_id(*combined, dup);
-				if (ret)
-					goto err_out;
-			}
-		}
-	}
-	return 0;
-err_out:
-	expr__ctx_free(*combined);
-	*combined = NULL;
-	return ret;
-}
-
 /**
  * parse_ids - Build the event string for the ids and parse them creating an
  *             evlist. The encoded metric_ids are decoded.
- * @metric_no_merge: is metric sharing explicitly disabled.
  * @fake_pmu: use a fake PMU when testing metrics not supported by the current CPU.
  * @ids: the event identifiers parsed from a metric.
  * @modifier: any modifiers added to the events.
  * @group_events: should events be placed in a weak group.
- * @tool_events: entries set true if the tool event of index could be present in
- *               the overall list of metrics.
  * @out_evlist: the created list of events.
+ * @filter_pmu: only parse events of this PMU, or NULL for all PMUs.
+ * @cputype_filter: is @filter_pmu from --cputype.
  */
-static int parse_ids(bool metric_no_merge, bool fake_pmu,
-		     struct expr_parse_ctx *ids, const char *modifier,
-		     bool group_events, const bool tool_events[TOOL_PMU__EVENT_MAX],
-		     struct evlist **out_evlist,
-		     const char *filter_pmu,
-		     bool cputype_filter)
+static int parse_ids(bool fake_pmu, struct expr_parse_ctx *ids, const char *modifier,
+		     bool group_events, struct evlist **out_evlist,
+		     const char *filter_pmu, bool cputype_filter)
 {
 	struct parse_events_error parse_error;
 	struct evlist *parsed_evlist;
@@ -1425,39 +1870,18 @@ static int parse_ids(bool metric_no_merge, bool fake_pmu,
 	int ret;
 
 	*out_evlist = NULL;
-	if (!metric_no_merge || hashmap__size(ids->ids) == 0) {
-		bool added_event = false;
-		int i;
+	if (hashmap__size(ids->ids) == 0) {
 		/*
-		 * We may fail to share events between metrics because a tool
-		 * event isn't present in one metric. For example, a ratio of
-		 * cache misses doesn't need duration_time but the same events
-		 * may be used for a misses per second. Events without sharing
-		 * implies multiplexing, that is best avoided, so place
-		 * all tool events in every group.
-		 *
-		 * Also, there may be no ids/events in the expression parsing
-		 * context because of constant evaluation, e.g.:
+		 * There may be no ids/events in the expression parsing context
+		 * because of constant evaluation, e.g.:
 		 *    event1 if #smt_on else 0
 		 * Add a tool event to avoid a parse error on an empty string.
 		 */
-		tool_pmu__for_each_event(i) {
-			if (tool_events[i]) {
-				char *tmp = strdup(tool_pmu__event_to_str(i));
-
-				if (!tmp)
-					return -ENOMEM;
-				ids__insert(ids->ids, tmp);
-				added_event = true;
-			}
-		}
-		if (!added_event && hashmap__size(ids->ids) == 0) {
-			char *tmp = strdup("duration_time");
+		char *tmp = strdup("duration_time");
 
-			if (!tmp)
-				return -ENOMEM;
-			ids__insert(ids->ids, tmp);
-		}
+		if (!tmp)
+			return -ENOMEM;
+		ids__insert(ids->ids, tmp);
 	}
 	ret = metricgroup__build_event_string(&events, ids, modifier,
 					      group_events);
@@ -1546,6 +1970,336 @@ static struct evsel *pick_display_evsel(struct list_head *metric_list,
 	return selected;
 }
 
+/* Find @evsel in @own and return its replacement in @map, or itself if none. */
+static struct evsel *own_evsel__mapped(struct evsel **own, struct evsel **map, int nr,
+				       struct evsel *evsel)
+{
+	for (int i = 0; i < nr; i++) {
+		if (own[i] == evsel)
+			return map[i] ?: evsel;
+	}
+	return evsel;
+}
+
+/* Add @evsel to the end of the group in @evlist led by @leader. */
+static void evlist__add_to_group(struct evlist *evlist, struct evsel *leader,
+				 struct evsel *evsel)
+{
+	struct evsel *pos, *last = leader;
+
+	for_each_group_evsel(pos, leader)
+		last = pos;
+
+	evlist__add(evlist, evsel);
+	if (list_empty(&evsel->core.node))
+		return;
+
+	evsel__set_leader(evsel, leader);
+	evsel->core.nr_members = 0;
+	if (leader->core.nr_members < 1)
+		leader->core.nr_members = 1;
+	leader->core.nr_members++;
+
+	if (list_next_entry(last, core.node) != evsel) {
+		int idx = 0;
+
+		list_move(&evsel->core.node, &last->core.node);
+		evlist__for_each_entry(evlist, pos) {
+			pos->core.idx = idx++;
+			pos->tracking = !pos->core.idx;
+		}
+	}
+}
+
+/*
+ * Match a group created for arch_evsel__must_be_in_group() events, such as x86
+ * topdown slots and perf-metrics events, to an existing such group, marking any
+ * new members to be appended to it so ungrouped topdown metrics share one group.
+ */
+static void metric__match_forced_group(struct evsel_matcher *matcher, struct evsel *ev,
+				       struct evsel **own, struct evsel **map,
+				       struct evsel **extend_leader, int nr, int start)
+{
+	int metric_sources;
+	const struct evsel *mapped_leader;
+	struct evsel *pos_leader;
+
+	own_evsel__metric_match_info(own, map, nr, ev, &metric_sources, &mapped_leader);
+	pos_leader = evsel_matcher__find_for_metric(matcher, ev, /*leader=*/NULL,
+						    metric_sources, mapped_leader,
+						    /*forced_group_leader=*/true);
+	if (!pos_leader)
+		return;
+
+	map[start] = pos_leader;
+	for (int j = start + 1; j < nr && evsel__leader(own[j]) == ev; j++) {
+		own_evsel__metric_match_info(own, map, nr, own[j],
+					     &metric_sources, &mapped_leader);
+		map[j] = evsel_matcher__find_for_metric(matcher, own[j], pos_leader,
+							metric_sources, mapped_leader,
+							/*forced_group_leader=*/false);
+		if (map[j])
+			continue;
+		if (evsel_matcher__find(matcher, own[j], pos_leader)) {
+			for (int k = start; k < j; k++) {
+				map[k] = NULL;
+				extend_leader[k] = NULL;
+			}
+			return;
+		}
+		extend_leader[j] = pos_leader;
+	}
+}
+
+/* Match an ungrouped event (and any multi-PMU siblings) to an earlier event of this metric. */
+static bool metric__match_ungrouped_intra(struct evsel_matcher *matcher, struct evsel **own,
+					  struct evsel **map, struct evsel **extend_leader,
+					  int nr, int idx)
+{
+	struct evsel *ev = own[idx];
+	const struct evsel *mapped_leader;
+	struct evsel *cand;
+	int metric_sources;
+
+	if (!evsel__is_group_leader(ev) || ev->core.nr_members > 1 ||
+	    (ev->metric_leader && ev->metric_leader != ev))
+		return false;
+
+	own_evsel__metric_match_info(own, map, nr, ev, &metric_sources, &mapped_leader);
+	cand = evsel_matcher__find_for_metric(matcher, ev, /*leader=*/NULL,
+					      metric_sources, mapped_leader,
+					      /*forced_group_leader=*/false);
+	if (!cand)
+		return false;
+
+	for (int j = idx + 1; metric_sources > 1 && j < nr; j++) {
+		if (own[j]->metric_leader != ev)
+			continue;
+		if (map[j] || extend_leader[j] || !evsel__is_group_leader(own[j]) ||
+		    own[j]->core.nr_members > 1 ||
+		    !evsel_matcher__find_for_metric(matcher, own[j], /*leader=*/NULL,
+						    metric_sources, cand,
+						    /*forced_group_leader=*/false))
+			return false;
+	}
+
+	map[idx] = cand;
+	for (int j = idx + 1; metric_sources > 1 && j < nr; j++) {
+		if (own[j]->metric_leader == ev) {
+			map[j] = evsel_matcher__find_for_metric(matcher, own[j], /*leader=*/NULL,
+								metric_sources, cand,
+								/*forced_group_leader=*/false);
+		}
+	}
+	return true;
+}
+
+/**
+ * metric__adopt_events - Move the events parsed for @m to @perf_evlist.
+ * @m: The metric whose events, in m->evlist, are moved.
+ * @perf_evlist: The evlist the metric's events are added to.
+ * @matcher: If not NULL, a matcher of the events of @perf_evlist. An event of
+ *           @perf_evlist counting the same thing as an event of the metric,
+ *           such as from an earlier metric, an earlier metric group or the
+ *           command line, is used instead of the metric's event. A group of
+ *           the metric is only replaced if all of its events are found in one
+ *           group of @perf_evlist, unless grouping was only forced by the
+ *           architecture (e.g. x86 topdown perf-metrics), in which case new
+ *           members are added to the existing forced group.
+ *
+ * The metric's metric_events, and the metric leaders of the events, are
+ * updated to refer to the events used in @perf_evlist.
+ */
+static int metric__adopt_events(struct metric *m, struct evlist *perf_evlist,
+				struct evsel_matcher *matcher)
+{
+	int nr = evlist__nr_entries(m->evlist);
+	struct evsel **own, **map, **extend_leader;
+	struct evsel *ev;
+	bool changed;
+	int i = 0, j, ret = 0;
+
+	if (!nr)
+		return 0;
+
+	own = calloc(nr, sizeof(*own));
+	map = calloc(nr, sizeof(*map));
+	extend_leader = calloc(nr, sizeof(*extend_leader));
+	if (!own || !map || !extend_leader) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	evlist__for_each_entry(m->evlist, ev)
+		own[i++] = ev;
+
+	for (i = 0; matcher && i < nr; i++) {
+		int metric_sources;
+		const struct evsel *mapped_leader;
+
+		ev = own[i];
+		if (!evsel__is_group_leader(ev))
+			continue;
+
+		if (ev->core.nr_members > 1) {
+			if (!evsel_matcher__find_group(matcher, ev, own, map, nr, &map[i]) &&
+			    !m->group_events && evsel__is_forced_group(ev)) {
+				metric__match_forced_group(matcher, ev, own, map,
+							   extend_leader, nr, i);
+			}
+		} else {
+			own_evsel__metric_match_info(own, map, nr, ev,
+						     &metric_sources, &mapped_leader);
+			map[i] = evsel_matcher__find_for_metric(matcher, ev, /*leader=*/NULL,
+								metric_sources, mapped_leader,
+								/*forced_group_leader=*/false);
+		}
+	}
+
+	/*
+	 * All events sharing a metric_leader across PMUs must either be
+	 * replaced together or kept together so metric_leader and source_count
+	 * remain consistent.
+	 */
+	do {
+		changed = false;
+		for (i = 0; i < nr; i++) {
+			struct evsel *ml = own[i]->metric_leader;
+			int l_idx = -1;
+
+			if (!ml || ml == own[i])
+				continue;
+			for (j = 0; j < nr; j++) {
+				if (own[j] == ml) {
+					l_idx = j;
+					break;
+				}
+			}
+			if (l_idx < 0 || !map[i] == !map[l_idx])
+				continue;
+			for (j = 0; j < nr; j++) {
+				struct evsel *grp;
+
+				if (own[j]->metric_leader != ml)
+					continue;
+				grp = evsel__leader(own[j]);
+				for (int k = 0; k < nr; k++) {
+					if (evsel__leader(own[k]) == grp &&
+					    (map[k] || extend_leader[k])) {
+						map[k] = NULL;
+						extend_leader[k] = NULL;
+						changed = true;
+					}
+				}
+			}
+		}
+	} while (changed);
+
+	/* Move the events not replaced, making them available to match. */
+	for (i = 0; i < nr; i++) {
+		if (map[i])
+			continue;
+		if (matcher && extend_leader[i]) {
+			int metric_sources;
+			const struct evsel *mapped_leader;
+
+			own_evsel__metric_match_info(own, map, nr, own[i],
+						     &metric_sources, &mapped_leader);
+			map[i] = evsel_matcher__find_for_metric(matcher, own[i], extend_leader[i],
+								metric_sources, mapped_leader,
+								/*forced_group_leader=*/false);
+			if (map[i]) {
+				extend_leader[i] = NULL;
+				continue;
+			}
+		} else if (matcher &&
+			   metric__match_ungrouped_intra(matcher, own, map, extend_leader, nr, i)) {
+			continue;
+		}
+		evlist__remove(m->evlist, own[i]);
+		if (extend_leader[i])
+			evlist__add_to_group(perf_evlist, extend_leader[i], own[i]);
+		else
+			evlist__add(perf_evlist, own[i]);
+		if (matcher && !list_empty(&own[i]->core.node)) {
+			ret = evsel_matcher__add(matcher, own[i]);
+			if (ret)
+				goto out;
+		}
+	}
+
+	/*
+	 * Update first_wildcard_match on kept events if the first match was
+	 * replaced and will be freed.
+	 */
+	for (i = 0; i < nr; i++) {
+		struct evsel *fwm = own[i]->first_wildcard_match;
+
+		if (map[i] || !fwm)
+			continue;
+		if (own_evsel__mapped(own, map, nr, fwm) == fwm)
+			continue;
+		own[i]->first_wildcard_match = NULL;
+		for (j = i + 1; j < nr; j++) {
+			if (own[j]->first_wildcard_match == fwm)
+				own[j]->first_wildcard_match = own[i];
+		}
+	}
+
+	/* Fix up the metric ids and metric leaders of the events. */
+	for (i = 0; i < nr; i++) {
+		struct evsel *leader = own[i]->metric_leader;
+
+		if (leader)
+			leader = own_evsel__mapped(own, map, nr, leader);
+		if (!map[i]) {
+			own[i]->metric_leader = leader;
+			continue;
+		}
+		pr_debug("Using event %s for metric %s's event %s\n",
+			 evsel__name(map[i]), m->metric_name, evsel__name(own[i]));
+		ret = evsel__copy_metric_ids(map[i], own[i]);
+		if (ret)
+			goto out;
+		if (own[i]->collect_stat)
+			map[i]->collect_stat = true;
+		if (leader && !map[i]->metric_leader)
+			map[i]->metric_leader = leader;
+	}
+
+	/*
+	 * Different events of the metric may be replaced by the same event,
+	 * whose metric ids are those of both. Only keep the first so the
+	 * event's value isn't counted more than once.
+	 */
+	for (i = 0, j = 0; m->metric_events[i]; i++) {
+		struct evsel *mapped = own_evsel__mapped(own, map, nr, m->metric_events[i]);
+		bool dup = false;
+
+		for (int k = 0; k < j; k++) {
+			if (m->metric_events[k] == mapped) {
+				dup = true;
+				break;
+			}
+		}
+		if (!dup)
+			m->metric_events[j++] = mapped;
+	}
+	m->metric_events[j] = NULL;
+
+	/* Free the replaced events. */
+	for (i = 0; i < nr; i++) {
+		if (map[i]) {
+			evlist__remove(m->evlist, own[i]);
+			evsel__put(own[i]);
+		}
+	}
+out:
+	free(own);
+	free(map);
+	free(extend_leader);
+	return ret;
+}
+
 static int parse_groups(struct evlist *perf_evlist,
 			const char *pmu, bool cputype_filter, const char *str,
 			bool metric_no_group,
@@ -1557,13 +2311,21 @@ static int parse_groups(struct evlist *perf_evlist,
 			const struct pmu_metrics_table *table)
 {
 	struct perf_pmus *pmus = perf_env__pmus(evlist__env(perf_evlist) ?: host_env());
-	struct evlist *combined_evlist = NULL;
+	struct evsel_matcher matcher;
 	LIST_HEAD(metric_list);
 	struct metric *m;
-	bool tool_events[TOOL_PMU__EVENT_MAX] = {false};
 	bool is_default = !strcmp(str, "Default");
+	const char *filter_pmu = (pmu && strcmp(pmu, "all") == 0) ? NULL : pmu;
 	int ret;
 
+	/*
+	 * Unless sharing is disabled, metrics use the events of other metrics,
+	 * and those already in perf_evlist, that count the same thing.
+	 */
+	ret = evsel_matcher__init(&matcher, perf_evlist);
+	if (ret)
+		goto out;
+
 	ret = metricgroup__add_metric_list(pmus, pmu, str, metric_no_group, metric_no_threshold,
 					   user_requested_cpu_list,
 					   system_wide, fake_pmu,
@@ -1571,28 +2333,27 @@ static int parse_groups(struct evlist *perf_evlist,
 	if (ret)
 		goto out;
 
-	/* Sort metrics from largest to smallest. */
+	/*
+	 * Sort metrics from largest to smallest so that the groups of smaller
+	 * metrics can be found within the groups of larger ones.
+	 */
 	list_sort(NULL, &metric_list, metric_list_cmp);
 
-	if (!metric_no_merge) {
-		struct expr_parse_ctx *combined = NULL;
-
-		find_tool_events(&metric_list, tool_events);
-
-		ret = build_combined_expr_ctx(&metric_list, &combined);
+	list_for_each_entry(m, &metric_list, nd) {
+		ret = parse_ids(fake_pmu, m->pctx, m->modifier, m->group_events, &m->evlist,
+				filter_pmu, cputype_filter);
+		if (ret)
+			goto out;
 
-		if (!ret && combined && hashmap__size(combined->ids)) {
-			ret = parse_ids(metric_no_merge, fake_pmu, combined,
-					/*modifier=*/NULL,
-					/*group_events=*/false,
-					tool_events,
-					&combined_evlist,
-					(pmu && strcmp(pmu, "all") == 0) ? NULL : pmu,
-					cputype_filter);
+		ret = setup_metric_events(fake_pmu ? "all" : m->pmu, m->pctx->ids,
+					  m->evlist, &m->metric_events);
+		if (ret) {
+			pr_err("Cannot resolve IDs for %s: %s\n",
+				m->metric_name, m->metric_expr);
+			goto out;
 		}
-		if (combined)
-			expr__ctx_free(combined);
 
+		ret = metric__adopt_events(m, perf_evlist, metric_no_merge ? NULL : &matcher);
 		if (ret)
 			goto out;
 	}
@@ -1602,71 +2363,19 @@ static int parse_groups(struct evlist *perf_evlist,
 
 	list_for_each_entry(m, &metric_list, nd) {
 		struct metric_event *me;
-		struct evsel **metric_events;
-		struct evlist *metric_evlist = NULL;
-		struct metric *n;
 		struct metric_expr *expr;
 
-		if (combined_evlist && !m->group_events) {
-			metric_evlist = combined_evlist;
-		} else if (!metric_no_merge) {
-			/*
-			 * See if the IDs for this metric are a subset of an
-			 * earlier metric.
-			 */
-			list_for_each_entry(n, &metric_list, nd) {
-				if (m == n)
-					break;
-
-				if (n->evlist == NULL)
-					continue;
-
-				if ((!m->modifier && n->modifier) ||
-				    (m->modifier && !n->modifier) ||
-				    (m->modifier && n->modifier &&
-					    strcmp(m->modifier, n->modifier)))
-					continue;
-
-				if ((!m->pmu && n->pmu) ||
-				    (m->pmu && !n->pmu) ||
-				    (m->pmu && n->pmu && strcmp(m->pmu, n->pmu)))
-					continue;
-
-				if (expr__subset_of_ids(n->pctx, m->pctx)) {
-					pr_debug("Events in '%s' fully contained within '%s'\n",
-						 m->metric_name, n->metric_name);
-					metric_evlist = n->evlist;
-					break;
-				}
-
-			}
-		}
-		if (!metric_evlist) {
-			ret = parse_ids(metric_no_merge, fake_pmu, m->pctx, m->modifier,
-					m->group_events, tool_events, &m->evlist,
-					(pmu && strcmp(pmu, "all") == 0) ? NULL : pmu,
-					cputype_filter);
-			if (ret)
-				goto out;
-
-			metric_evlist = m->evlist;
-		}
-		ret = setup_metric_events(fake_pmu ? "all" : m->pmu, m->pctx->ids,
-					  metric_evlist, &metric_events);
-		if (ret) {
-			pr_err("Cannot resolve IDs for %s: %s\n",
-				m->metric_name, m->metric_expr);
-			goto out;
-		}
-
 		me = metricgroup__lookup(evlist__metric_events(perf_evlist),
-					 pick_display_evsel(&metric_list, metric_events),
+					 pick_display_evsel(&metric_list, m->metric_events),
 					 /*create=*/true);
+		if (!me) {
+			ret = -ENOMEM;
+			goto out;
+		}
 
 		expr = malloc(sizeof(struct metric_expr));
 		if (!expr) {
 			ret = -ENOMEM;
-			free(metric_events);
 			goto out;
 		}
 
@@ -1685,44 +2394,32 @@ static int parse_groups(struct evlist *perf_evlist,
 
 		if (!expr->metric_name) {
 			ret = -ENOMEM;
+			free(expr->metric_refs);
 			free(expr);
-			free(metric_events);
 			goto out;
 		}
 		if (m->default_show_events) {
 			struct evsel *pos;
 
-			for (int i = 0; metric_events[i]; i++)
-				metric_events[i]->default_show_events = true;
-			evlist__for_each_entry(metric_evlist, pos) {
+			for (int i = 0; m->metric_events[i]; i++)
+				m->metric_events[i]->default_show_events = true;
+			evlist__for_each_entry(perf_evlist, pos) {
 				if (pos->metric_leader && pos->metric_leader->default_show_events)
 					pos->default_show_events = true;
 			}
 		}
 		expr->metric_threshold = m->metric_threshold;
 		expr->metric_unit = m->metric_unit;
-		expr->metric_events = metric_events;
+		expr->metric_events = m->metric_events;
+		m->metric_events = NULL;
 		expr->runtime = m->pctx->sctx.runtime;
 		expr->default_metricgroup_name = m->default_metricgroup_name;
 		me->is_default = is_default;
 		list_add(&expr->nd, &me->head);
 	}
 
-
-	if (combined_evlist) {
-		evlist__splice_list_tail(perf_evlist, &evlist__core(combined_evlist)->entries);
-		evlist__put(combined_evlist);
-		combined_evlist = NULL;
-	}
-
-	list_for_each_entry(m, &metric_list, nd) {
-		if (m->evlist)
-			evlist__splice_list_tail(perf_evlist, &evlist__core(m->evlist)->entries);
-	}
-
 out:
-	if (combined_evlist)
-		evlist__put(combined_evlist);
+	evsel_matcher__exit(&matcher);
 	metricgroup__free_metrics(&metric_list);
 	return ret;
 }
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 24/30] perf stat: Share events between metrics and command line events
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (22 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 23/30] perf metricgroup: Share metric events by their counter encoding Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:14   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 25/30] perf pmus: Add perf_pmus__read_from_env() Ian Rogers
                   ` (5 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

The metrics added by -T, --smi-cost, --topdown and the Default metric
groups were parsed into a separate evlist and then added to the events
from the command line, so they didn't share events with them or, for
the Default metric groups, with each other.

Parse them directly into evsel_list so that the evsel matcher of
metricgroup__parse_groups() shares events counting the same thing. Only
mark the newly added events as skippable and keep the evsel indices in
list order after sorting the Default metric events, as required when
writing the group descriptions of a perf.data file. This removes the
copying of the metric events from the separate evlist with
metricgroup__copy_metric_events(), which crashed for "perf stat -T" on
an Alder Lake system.

For example, "perf stat -e duration_time -T" now uses the duration_time
event from the command line for the transaction metrics.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/builtin-stat.c | 72 +++++++++++++++++++++++----------------
 1 file changed, 43 insertions(+), 29 deletions(-)

diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index f81192cec481..2b563382cf43 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -1975,6 +1975,15 @@ static int default_evlist_evsel_cmp(void *priv __maybe_unused,
 		      evsel__name((struct evsel *)rhs_leader));
 }
 
+static bool evsel_in_array(const struct evsel *evsel, struct evsel * const *evsels, int nr)
+{
+	for (int i = 0; i < nr; i++) {
+		if (evsels[i] == evsel)
+			return true;
+	}
+	return false;
+}
+
 /*
  * Add default events, if there were no attributes specified or
  * if -d/--detailed, -d -d or -d -d -d is used:
@@ -1984,18 +1993,31 @@ static int add_default_events(void)
 	const char *pmu = parse_events_option_args.pmu_filter ?: "all";
 	struct perf_pmus *pmus = perf_env__pmus(host_env());
 	struct parse_events_error err;
-	struct evlist *evlist = evlist__new();
+	/*
+	 * Add the metrics directly to evsel_list so that they share events
+	 * with the events and metrics already there.
+	 */
+	struct evlist *evlist = evsel_list;
+	int nr_orig_entries = evlist__nr_entries(evlist);
+	struct evsel **orig_evsels = NULL;
 	struct evsel *evsel;
-	int ret = 0;
+	int idx = 0, ret = 0;
 
-	if (!evlist)
-		return -ENOMEM;
+	/* Set attrs if no event is selected and !null_run: */
+	if (stat_config.null_run)
+		return 0;
 
 	parse_events_error__init(&err);
 
-	/* Set attrs if no event is selected and !null_run: */
-	if (stat_config.null_run)
-		goto out;
+	if (nr_orig_entries > 0) {
+		orig_evsels = calloc(nr_orig_entries, sizeof(*orig_evsels));
+		if (!orig_evsels) {
+			ret = -ENOMEM;
+			goto out;
+		}
+		evlist__for_each_entry(evlist, evsel)
+			orig_evsels[idx++] = evsel;
+	}
 
 	if (transaction_run) {
 		/* Handle -T as -M transaction. Once platform specific metrics
@@ -2101,7 +2123,7 @@ static int add_default_events(void)
 	if (!stat_config.topdown_level)
 		stat_config.topdown_level = 1;
 
-	if (!evlist__nr_entries(evlist) && !evlist__nr_entries(evsel_list)) {
+	if (!evlist__nr_entries(evlist)) {
 		/*
 		 * Add Default metrics. To minimize multiplexing, don't request
 		 * threshold computation, but it will be computed if the events
@@ -2112,8 +2134,6 @@ static int add_default_events(void)
 		};
 
 		for (size_t i = 0; i < ARRAY_SIZE(default_metricgroup_names); i++) {
-			struct evlist *metric_evlist;
-
 			if (!metricgroup__has_metric_or_groups(pmus, pmu,
 							       default_metricgroup_names[i]))
 				continue;
@@ -2121,12 +2141,7 @@ static int add_default_events(void)
 			if ((int)i > detailed_run)
 				break;
 
-			metric_evlist = evlist__new();
-			if (!metric_evlist) {
-				ret = -ENOMEM;
-				break;
-			}
-			if (metricgroup__parse_groups(metric_evlist, pmu,
+			if (metricgroup__parse_groups(evlist, pmu,
 						      parse_events_option_args.cputype_filter,
 						      default_metricgroup_names[i],
 							/*metric_no_group=*/false,
@@ -2135,26 +2150,29 @@ static int add_default_events(void)
 							stat_config.user_requested_cpu_list,
 							stat_config.system_wide,
 							stat_config.hardware_aware_grouping) < 0) {
-				evlist__put(metric_evlist);
 				ret = -1;
 				break;
 			}
 
-			evlist__for_each_entry(metric_evlist, evsel)
+			/* evsel_list was empty so all the events are for the Default metrics. */
+			evlist__for_each_entry(evlist, evsel)
 				evsel->default_metricgroup = true;
-
-			evlist__splice_list_tail(evlist, &evlist__core(metric_evlist)->entries);
-			metricgroup__copy_metric_events(evlist, /*cgrp=*/NULL,
-							evlist__metric_events(evlist),
-							evlist__metric_events(metric_evlist));
-			evlist__put(metric_evlist);
 		}
 		list_sort(/*priv=*/NULL, &evlist__core(evlist)->entries, default_evlist_evsel_cmp);
+		/* Keep the indices in list order, as required by the perf.data header. */
+		idx = 0;
+		evlist__for_each_entry(evlist, evsel) {
+			evsel->core.idx = idx++;
+			evsel->tracking = !evsel->core.idx;
+		}
 
 	}
 out:
 	if (!ret) {
 		evlist__for_each_entry(evlist, evsel) {
+			/* Only events added here, not those from the command line. */
+			if (evsel_in_array(evsel, orig_evsels, nr_orig_entries))
+				continue;
 			/*
 			 * Make at least one event non-skippable so fatal errors are visible.
 			 * 'cycles' always used to be default and non-skippable, so use that.
@@ -2163,12 +2181,8 @@ static int add_default_events(void)
 				evsel->skippable = true;
 		}
 	}
+	free(orig_evsels);
 	parse_events_error__exit(&err);
-	evlist__splice_list_tail(evsel_list, &evlist__core(evlist)->entries);
-	metricgroup__copy_metric_events(evsel_list, /*cgrp=*/NULL,
-					evlist__metric_events(evsel_list),
-					evlist__metric_events(evlist));
-	evlist__put(evlist);
 	return ret;
 }
 
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 25/30] perf pmus: Add perf_pmus__read_from_env()
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (23 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 24/30] perf stat: Share events between metrics and command line events Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 21:05   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 26/30] perf metricgroup: Improve matching of metrics to recorded events Ian Rogers
                   ` (4 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Add perf_pmus__read_from_env() and perf_pmu__add_cap() to reconstruct
core_pmus and other_pmus lists and their capabilities in a
struct perf_pmus from HEADER_PMU_MAPPINGS, HEADER_HYBRID_TOPOLOGY,
HEADER_PMU_CAPS, and HEADER_CPU_PMU_CAPS in a perf.data header
environment, and lazily populate env->pmus in perf_env__pmus(). An
environment without any PMU header metadata, such as one used while
recording, falls back to the live host's PMUs rather than re-reading
sysfs into its own copy.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/env.c    |  18 +++
 tools/perf/util/env.h    |   5 +
 tools/perf/util/header.c |  23 ++++
 tools/perf/util/pmu.c    |  31 ++++++
 tools/perf/util/pmu.h    |   9 ++
 tools/perf/util/pmus.c   | 234 +++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmus.h   |  15 ++-
 7 files changed, 334 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/env.c b/tools/perf/util/env.c
index fec6a5a8ecb6..04f4db90cc60 100644
--- a/tools/perf/util/env.c
+++ b/tools/perf/util/env.c
@@ -341,6 +341,24 @@ struct perf_env *host_env(void)
 
 struct perf_pmus *perf_env__pmus(struct perf_env *env)
 {
+	struct perf_env *host = host_env();
+
+	if (env == host ||
+	    !(env->nr_pmu_mappings || env->nr_hybrid_nodes ||
+	      env->nr_cpu_pmu_caps || env->nr_pmus_with_caps)) {
+		/* No PMU metadata of its own, use the live host's PMUs. */
+		return &host->pmus;
+	}
+
+	mutex_lock(&env->lock);
+	if (!env->pmus.core_pmus.next || !env->pmus.other_pmus.next)
+		perf_pmus__init(&env->pmus);
+	mutex_unlock(&env->lock);
+
+	mutex_lock(&env->pmus.lock);
+	if (env->pmus.read_pmu_types == 0)
+		perf_pmus__read_from_env(&env->pmus, env);
+	mutex_unlock(&env->pmus.lock);
 	return &env->pmus;
 }
 
diff --git a/tools/perf/util/env.h b/tools/perf/util/env.h
index e38e953060c9..ef583161583f 100644
--- a/tools/perf/util/env.h
+++ b/tools/perf/util/env.h
@@ -238,6 +238,11 @@ struct perf_env *host_env(void);
  * perf_env__pmus() - Return the struct perf_pmus associated with @env.
  * @env: Environment whose PMU collection to retrieve.
  *
+ * For non-host environments with PMU header metadata (HEADER_PMU_MAPPINGS,
+ * HEADER_HYBRID_TOPOLOGY, HEADER_CPU_PMU_CAPS or HEADER_PMU_CAPS), lazily
+ * populates @env->pmus from the perf.data header on first access. If @env has
+ * no PMU metadata of its own, falls back to the live host's PMU collection.
+ *
  * Return: Pointer to the struct perf_pmus for @env.
  */
 struct perf_pmus *perf_env__pmus(struct perf_env *env);
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
index a442f2a904a9..3708a311e805 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -5765,6 +5765,29 @@ int perf_event__process_feature(const struct perf_tool *tool __maybe_unused,
 		goto out;
 	}
 
+	switch (feat) {
+	case HEADER_ARCH:
+	case HEADER_CPUID:
+	case HEADER_NRCPUS:
+	case HEADER_PMU_MAPPINGS:
+	case HEADER_CPU_PMU_CAPS:
+	case HEADER_HYBRID_TOPOLOGY:
+	case HEADER_PMU_CAPS:
+	case HEADER_E_MACHINE:
+		mutex_lock(&header->env.pmus.lock);
+		if (session->evlist) {
+			struct evsel *evsel;
+
+			evlist__for_each_entry(session->evlist, evsel)
+				evsel->pmu = NULL;
+		}
+		perf_pmus__exit(&header->env.pmus);
+		mutex_unlock(&header->env.pmus.lock);
+		break;
+	default:
+		break;
+	}
+
 	if (session->tool->show_feat_hdr) {
 		if (!feat_ops[feat].full_only ||
 		    session->tool->show_feat_hdr >= SHOW_FEAT_HEADER_FULL_INFO) {
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 381189acfd4a..6db7d7df9c26 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -2573,6 +2573,37 @@ static int perf_pmu__new_caps(struct list_head *list, char *name, char *value)
 	return -ENOMEM;
 }
 
+int perf_pmu__add_cap(struct perf_pmu *pmu, const char *name, const char *value)
+{
+	struct perf_pmu_caps *caps;
+
+	if (!pmu || !name || !value)
+		return -EINVAL;
+	if (perf_pmu__get_cap(pmu, name))
+		return 0;
+
+	caps = zalloc(sizeof(*caps));
+	if (!caps)
+		return -ENOMEM;
+
+	caps->name = strdup(name);
+	if (!caps->name) {
+		free(caps);
+		return -ENOMEM;
+	}
+	caps->value = strdup(value);
+	if (!caps->value) {
+		zfree(&caps->name);
+		free(caps);
+		return -ENOMEM;
+	}
+	list_add_tail(&caps->list, &pmu->caps);
+	pmu->nr_caps++;
+	if (!strcmp(name, "max_precise"))
+		pmu->max_precise = atoi(value);
+	return 0;
+}
+
 static void perf_pmu__del_caps(struct perf_pmu *pmu)
 {
 	struct perf_pmu_caps *caps, *tmp;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index ab7123964423..b556ec51fdeb 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -334,6 +334,15 @@ bool pmu_uncore_identifier_match(const char *compat, const char *id);
 int perf_pmu__convert_scale(const char *scale, char **end, double *sval);
 
 struct perf_pmu_caps *perf_pmu__get_cap(struct perf_pmu *pmu, const char *name);
+/**
+ * perf_pmu__add_cap() - Add a name/value capability entry to @pmu->caps.
+ * @pmu: The PMU to add the capability to.
+ * @name: Capability name.
+ * @value: Capability value string.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int perf_pmu__add_cap(struct perf_pmu *pmu, const char *name, const char *value);
 
 int perf_pmu__caps_parse(struct perf_pmu *pmu);
 
diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
index 6d68371107aa..b924ff6180e4 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -7,13 +7,16 @@
 #include <subcmd/pager.h>
 #include <sys/types.h>
 #include <ctype.h>
+#include <errno.h>
 #include <pthread.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include "cpumap.h"
 #include "debug.h"
 #include "drm_pmu.h"
+#include "dwarf-regs.h"
 #include "env.h"
 #include "evsel.h"
 #include "pmus.h"
@@ -981,3 +984,234 @@ struct perf_pmu *perf_pmus__fake_pmu(void)
 
 	return &fake;
 }
+
+/*
+ * Determine whether a PMU name reconstructed from perf.data headers is a
+ * core PMU, checking common core PMU prefixes, hybrid topology nodes, and
+ * the host architecture's is_pmu_core() when architectures match.
+ */
+static bool env_is_pmu_core(const char *name, struct perf_env *env)
+{
+	if (!strcmp(name, "cpu") || !strcmp(name, "cpum_cf") ||
+	    !strcmp(name, "default_core") || !strcmp(name, "arc_pct") ||
+	    strstarts(name, "cpu_") || strstarts(name, "armv") ||
+	    (strstarts(name, "apple_") && strstr(name, "_pmu")))
+		return true;
+
+	if (env && env->hybrid_nodes) {
+		for (int i = 0; i < env->nr_hybrid_nodes; i++) {
+			const char *pmu_name = env->hybrid_nodes[i].pmu_name;
+
+			if (pmu_name && !strcmp(name, pmu_name))
+				return true;
+		}
+		return false;
+	}
+
+	if (!env || e_machine__same_arch(perf_env__e_machine(env, /*e_flags=*/NULL),
+					 perf_env__e_machine(host_env(), /*e_flags=*/NULL)))
+		return is_pmu_core(name);
+
+	return false;
+}
+
+/*
+ * Look up @name in @pmus or allocate and initialize a new perf_env-backed
+ * struct perf_pmu, upgrading PERF_PMU_TYPE_FAKE if a real type is later
+ * discovered.
+ */
+static struct perf_pmu *env_pmu_find_or_add(struct perf_pmus *pmus,
+					    const char *name, __u32 type,
+					    struct perf_env *env,
+					    const struct pmu_events_table *core_table)
+{
+	struct perf_pmu *pmu = pmu_find(pmus, name);
+
+	if (type == PERF_PMU_TYPE_FAKE) {
+		if (!strcmp(name, "software"))
+			type = PERF_TYPE_SOFTWARE;
+		else if (!strcmp(name, "tracepoint"))
+			type = PERF_TYPE_TRACEPOINT;
+		else if (!strcmp(name, "breakpoint"))
+			type = PERF_TYPE_BREAKPOINT;
+		else if (!strcmp(name, "tool"))
+			type = PERF_PMU_TYPE_TOOL;
+	}
+
+	if (pmu) {
+		if (pmu->type == PERF_PMU_TYPE_FAKE && type != PERF_PMU_TYPE_FAKE) {
+			pmu->type = type;
+			pmu->is_uncore = false;
+			pmu->is_uncore = !pmu->is_core && !pmu->auxtrace &&
+					 !perf_pmu__is_software(pmu) &&
+					 perf_pmu__kind(pmu) == PERF_PMU_KIND_PE;
+			if (!pmu->is_core && !pmu->is_uncore &&
+			    pmu->events_table == core_table)
+				pmu->events_table = NULL;
+		}
+		return pmu;
+	}
+
+	pmu = zalloc(sizeof(*pmu));
+	if (!pmu)
+		return NULL;
+
+	if (perf_pmu__init(pmu, type, name, /*from_env=*/true) != 0) {
+		perf_pmu__delete(pmu);
+		return NULL;
+	}
+
+	pmu->pmus = pmus;
+	pmu->sysfs_aliases_loaded = true;
+	pmu->caps_initialized = true;
+	pmu->max_precise = -1;
+	pmu->is_core = env_is_pmu_core(name, env);
+	pmu->auxtrace = !strcmp(name, "intel_pt") ||
+			!strcmp(name, "intel_bts") ||
+			!strcmp(name, "cs_etm") ||
+			strstarts(name, "arm_spe") ||
+			strstarts(name, "hisi_ptt");
+	pmu->is_uncore = !pmu->is_core && !pmu->auxtrace &&
+			 !perf_pmu__is_software(pmu) &&
+			 (perf_pmu__kind(pmu) == PERF_PMU_KIND_PE ||
+			  pmu->type == PERF_PMU_TYPE_FAKE);
+
+	if (!strcmp(name, "software") || !strcmp(name, "tool"))
+		pmu->events_table = perf_pmu__default_core_events_table();
+	else if (core_table && (pmu->is_core || pmu->is_uncore))
+		pmu->events_table = core_table;
+
+	list_add_tail(&pmu->list, pmu->is_core ? &pmus->core_pmus : &pmus->other_pmus);
+	return pmu;
+}
+
+/* Parse "key=value" capability strings from perf_env into @pmu->caps. */
+static void env_pmu_add_caps_strings(struct perf_pmu *pmu, int nr_caps, char **caps)
+{
+	if (!pmu || nr_caps <= 0 || !caps)
+		return;
+
+	for (int i = 0; i < nr_caps; i++) {
+		const char *eq;
+
+		if (!caps[i])
+			continue;
+		eq = strchr(caps[i], '=');
+
+		if (eq) {
+			char *key = strndup(caps[i], eq - caps[i]);
+
+			if (key) {
+				perf_pmu__add_cap(pmu, key, eq + 1);
+				free(key);
+			}
+		} else {
+			perf_pmu__add_cap(pmu, caps[i], "");
+		}
+	}
+}
+
+int perf_pmus__read_from_env(struct perf_pmus *pmus, struct perf_env *env)
+{
+	const struct pmu_events_table *core_table = NULL;
+	struct perf_pmu *pmu;
+
+	if (!pmus || !env)
+		return -EINVAL;
+
+	perf_pmus__exit(pmus);
+	pmus->read_pmu_types = PERF_TOOL_PMU_TYPE_ALL_MASK;
+
+	if (env->cpuid)
+		core_table = find_core_events_table(perf_env__e_machine(env, /*e_flags=*/NULL),
+						    env->cpuid);
+
+	/* 1. Populate from HEADER_PMU_MAPPINGS */
+	if (env->nr_pmu_mappings > 0 && env->pmu_mappings) {
+		char *str = env->pmu_mappings;
+
+		for (int i = 0; i < env->nr_pmu_mappings; i++) {
+			char *colon = NULL;
+			unsigned long type = strtoul(str, &colon, 0);
+
+			if (!colon || *colon != ':')
+				break;
+			str = colon + 1;
+			env_pmu_find_or_add(pmus, str, (__u32)type, env, core_table);
+			str += strlen(str) + 1;
+		}
+	}
+
+	/* 2. Populate/enrich from HEADER_HYBRID_TOPOLOGY */
+	for (int i = 0; i < env->nr_hybrid_nodes; i++) {
+		struct hybrid_node *hn = &env->hybrid_nodes[i];
+
+		if (!hn->pmu_name)
+			continue;
+		pmu = env_pmu_find_or_add(pmus, hn->pmu_name, PERF_PMU_TYPE_FAKE,
+					  env, core_table);
+		if (!pmu)
+			continue;
+		if (!pmu->is_core) {
+			pmu->is_core = true;
+			pmu->is_uncore = false;
+			if (!pmu->events_table && core_table)
+				pmu->events_table = core_table;
+			list_move_tail(&pmu->list, &pmus->core_pmus);
+		}
+		if (hn->cpus && !pmu->cpus)
+			pmu->cpus = perf_cpu_map__new(hn->cpus);
+	}
+
+	/* 3. Populate/enrich from HEADER_PMU_CAPS */
+	for (int i = 0; i < env->nr_pmus_with_caps; i++) {
+		struct pmu_caps *pc = &env->pmu_caps[i];
+
+		if (!pc->pmu_name)
+			continue;
+		pmu = env_pmu_find_or_add(pmus, pc->pmu_name, PERF_PMU_TYPE_FAKE,
+					  env, core_table);
+		if (pmu)
+			env_pmu_add_caps_strings(pmu, pc->nr_caps, pc->caps);
+	}
+
+	/*
+	 * 4. Populate/enrich from HEADER_CPU_PMU_CAPS.
+	 * HEADER_CPU_PMU_CAPS holds the capabilities of the first core PMU,
+	 * named "cpu" on x86 but e.g. "armv8_pmuv3_0" or "cpum_cf" elsewhere.
+	 * On hybrid systems it duplicates the first core PMU's entry in
+	 * HEADER_PMU_CAPS. Attach the capabilities to "cpu" if present,
+	 * otherwise to the first core PMU when it has no capabilities yet,
+	 * creating a "cpu" PMU only if no core PMUs exist.
+	 */
+	if (env->nr_cpu_pmu_caps > 0 && env->cpu_pmu_caps) {
+		pmu = pmu_find(pmus, "cpu");
+		if (!pmu && !list_empty(&pmus->core_pmus)) {
+			/* Match the writer's perf_pmus__find_core_pmu() order. */
+			list_sort(NULL, &pmus->core_pmus, pmus_cmp);
+			pmu = list_first_entry(&pmus->core_pmus, struct perf_pmu, list);
+			if (pmu->nr_caps > 0)
+				pmu = NULL;
+		} else if (!pmu) {
+			pmu = env_pmu_find_or_add(pmus, "cpu", PERF_TYPE_RAW,
+						  env, core_table);
+		}
+		if (pmu)
+			env_pmu_add_caps_strings(pmu, env->nr_cpu_pmu_caps, env->cpu_pmu_caps);
+	}
+
+	/* 5. Set default core CPUs on non-hybrid systems */
+	if (env->nr_cpus_avail > 0 && env->nr_hybrid_nodes == 0) {
+		char buf[64];
+
+		snprintf(buf, sizeof(buf), "0-%d", env->nr_cpus_avail - 1);
+		list_for_each_entry(pmu, &pmus->core_pmus, list) {
+			if (!pmu->cpus)
+				pmu->cpus = perf_cpu_map__new(buf);
+		}
+	}
+
+	list_sort(NULL, &pmus->core_pmus, pmus_cmp);
+	list_sort(NULL, &pmus->other_pmus, pmus_cmp);
+	return 0;
+}
diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h
index 6e7c9f857aaf..2b784cf648f3 100644
--- a/tools/perf/util/pmus.h
+++ b/tools/perf/util/pmus.h
@@ -8,6 +8,7 @@
 #include "mutex.h"
 
 struct perf_event_attr;
+struct perf_env;
 struct perf_pmu;
 struct print_callbacks;
 
@@ -49,7 +50,10 @@ struct perf_pmus {
 	 * encoding in perf_event_attr::config is supported.
 	 */
 	enum perf_pmus_extended_type extended_type;
-	/** @lock: Protects lazy initialization of @extended_type. */
+	/**
+	 * @lock: Protects lazy initialization of @extended_type and
+	 * perf_pmus__read_from_env().
+	 */
 	struct mutex lock;
 };
 
@@ -75,6 +79,15 @@ void perf_pmus__init(struct perf_pmus *pmus);
  */
 void perf_pmus__exit(struct perf_pmus *pmus);
 
+/**
+ * perf_pmus__read_from_env() - Populate @pmus from perf.data header metadata.
+ * @pmus: The PMU collection to populate.
+ * @env: The perf_env containing header PMU mappings, hybrid topology, and caps.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int perf_pmus__read_from_env(struct perf_pmus *pmus, struct perf_env *env);
+
 /**
  * perf_pmus__find() - Find a PMU by name in @pmus.
  * @pmus: The PMU collection to search.
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 26/30] perf metricgroup: Improve matching of metrics to recorded events
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (24 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 25/30] perf pmus: Add perf_pmus__read_from_env() Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:37   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 27/30] perf metricgroup: Add metricgroup__add_recorded_metrics() Ian Rogers
                   ` (3 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Matching metrics to the events of a perf.data file compared only the
perf_event_attr type and config of events, which failed for common
cases:

 - Legacy hardware events, such as "cycles" with "perf record -e
   cycles", didn't match the sysfs event, such as "cpu_core/cpu-cycles/",
   that metrics are parsed to. Use the evsel matcher, as used for metrics
   sharing events, which compares the PMU name and counter encoding,
   converting legacy hardware events of core PMUs to the equivalent
   sysfs event's encoding. Add an EVSEL_MATCH_RECORDED mode to the
   matcher that tolerates the exclusion bits evsel__fallback() may have
   changed when recording, skips unsupported events and prefers events
   with counts.

 - A recorded event could be used by only one metric leader, as metrics
   set the event's metric_leader and later metrics with a different
   leader were skipped. Instead attach a copy of each metric expression
   to the recorded event of each PMU the metric applies to, adding the
   metric's event identifiers to the matched recorded evsels.

 - When the same counter was recorded more than once, such as for the
   default metrics of perf stat in different groups, the first was
   matched. Prefer a supported event with the same name and counts and,
   when the metric's events are grouped, the same group membership, as
   the matcher does.

 - On hybrid systems a metric was skipped unless the events of every
   core PMU were recorded. Add the metric for each PMU whose events were
   all recorded.

Also avoid adding duplicate metrics, avoid counting a recorded event
twice when different metric events match it, and avoid event grouping
warnings when parsing the metrics. Metrics of a single event are kept as
they may usefully scale the event, for example from cache lines to
bytes.

For example, on an Alder Lake system with legacy cycles and instructions
events recorded:

  $ perf record -e '{cycles,instructions}:S' -a sleep 1
  $ perf script -vv -F comm,metric 2>&1 | grep "Found metric" | \
    sed 's/ whose.*//' | sort | uniq
  Found metric 'insn_per_cycle'
  Found metric 'tma_info_core_cpi'
  Found metric 'tma_info_core_ipc'
  Found metric 'tma_info_inst_mix_instructions'
  Found metric 'tma_info_system_mux'
  Found metric 'tma_info_thread_clks'
  Found metric 'tma_info_thread_cpi'
  Found metric 'tma_info_thread_ipc'

where the json metrics use sysfs and json encodings of the events, such
as cpu_core/INST_RETIRED.ANY/, that now match the recorded legacy
events.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/metricgroup.c | 583 ++++++++++++++++++++++++++--------
 tools/perf/util/metricgroup.h |   5 +-
 2 files changed, 460 insertions(+), 128 deletions(-)

diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index f4a2b280091c..f41e11a9a3e7 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -8,10 +8,13 @@
 #include "metricgroup.h"
 #include "debug.h"
 #include "evlist.h"
+#include "env.h"
 #include "evsel.h"
+#include "counts.h"
 #include "strbuf.h"
 #include "pmu.h"
 #include "pmus.h"
+#include "parse-events.h"
 #include "print-events.h"
 #include "smt.h"
 #include "tool_pmu.h"
@@ -87,18 +90,24 @@ static struct rb_node *metric_event_new(struct rblist *rblist __maybe_unused,
 	return &me->nd;
 }
 
+static void metric_expr__delete(struct metric_expr *expr)
+{
+	if (!expr)
+		return;
+	zfree(&expr->metric_name);
+	zfree(&expr->metric_refs);
+	zfree(&expr->metric_events);
+	free(expr);
+}
+
 static void metric_event_delete(struct rblist *rblist __maybe_unused,
 				struct rb_node *rb_node)
 {
 	struct metric_event *me = container_of(rb_node, struct metric_event, nd);
 	struct metric_expr *expr, *tmp;
 
-	list_for_each_entry_safe(expr, tmp, &me->head, nd) {
-		zfree(&expr->metric_name);
-		zfree(&expr->metric_refs);
-		zfree(&expr->metric_events);
-		free(expr);
-	}
+	list_for_each_entry_safe(expr, tmp, &me->head, nd)
+		metric_expr__delete(expr);
 
 	free(me);
 }
@@ -481,12 +490,13 @@ int metricgroup__for_each_metric(struct perf_pmus *pmus, const struct pmu_metric
 
 /*
  * A key identifying the counter an evsel programs, used to find evsels counting
- * the same thing, such as when metrics share events.
+ * the same thing, such as when metrics share events or when matching evsels
+ * parsed from a metric with those read from a perf.data file.
  */
 struct counter_key {
 	/** @pmu_name: Name of the evsel's PMU, or NULL if unknown. */
 	const char *pmu_name;
-	/** @cgrp: The cgroup the evsel counts. */
+	/** @cgrp: The cgroup the evsel counts, compared by EVSEL_MATCH_LIVE. */
 	const struct cgroup *cgrp;
 	/** @type: perf_event_attr type, PERF_TYPE_RAW for any dynamic PMU type. */
 	__u32 type;
@@ -507,7 +517,7 @@ struct counter_key {
 	 * is counted, a mask of COUNTER_KEY_MOD_*.
 	 */
 	unsigned int modifiers;
-	/** @precise_ip: perf_event_attr precise_ip. */
+	/** @precise_ip: perf_event_attr precise_ip, compared by EVSEL_MATCH_LIVE. */
 	unsigned int precise_ip;
 };
 
@@ -517,6 +527,8 @@ struct counter_key {
 #define COUNTER_KEY_EXCLUDE_HOST	BIT(3)
 #define COUNTER_KEY_EXCLUDE_GUEST	BIT(4)
 #define COUNTER_KEY_EXCLUDE_IDLE	BIT(5)
+#define COUNTER_KEY_EXCLUDE_PRIV	(COUNTER_KEY_EXCLUDE_USER | COUNTER_KEY_EXCLUDE_KERNEL | \
+					 COUNTER_KEY_EXCLUDE_HV)
 
 #define COUNTER_KEY_MOD_PINNED		BIT(0)
 #define COUNTER_KEY_MOD_EXCLUSIVE	BIT(1)
@@ -533,6 +545,21 @@ struct legacy_hw_key {
 	struct counter_key key;
 };
 
+/** enum evsel_match_mode - How strictly evsels must agree to be matched. */
+enum evsel_match_mode {
+	/**
+	 * @EVSEL_MATCH_LIVE: Evsels parsed by this perf command, such as for
+	 * metrics sharing events, must agree on all modifiers.
+	 */
+	EVSEL_MATCH_LIVE,
+	/**
+	 * @EVSEL_MATCH_RECORDED: Evsels read from a perf.data file may have had
+	 * modifiers changed by evsel__fallback() and the counts of the evsels
+	 * are preferred.
+	 */
+	EVSEL_MATCH_RECORDED,
+};
+
 struct evsel_matcher_entry {
 	struct evsel *evsel;
 	struct counter_key key;
@@ -551,6 +578,13 @@ struct evsel_matcher {
 	size_t max_entries;
 	/** @legacy_hw_keys: Cache of legacy_hw_key. */
 	struct list_head legacy_hw_keys;
+	/** @mode: How strictly evsels must agree to be matched. */
+	enum evsel_match_mode mode;
+	/**
+	 * @match_grouping: Prefer evsels whose group membership is the same
+	 * as the evsel being matched.
+	 */
+	bool match_grouping;
 };
 
 static void counter_key__init_attr(struct counter_key *key, const char *pmu_name,
@@ -714,9 +748,12 @@ static void evsel_matcher__key(struct evsel_matcher *matcher, struct evsel *evse
 		legacy_hw_key(matcher, pmu->name, key->config, key);
 }
 
-/* Do @a and @b count the same thing? */
-static bool counter_key__equal(const struct counter_key *a, const struct counter_key *b)
+/* Do @a, from the evsel being matched, and @b count the same thing? */
+static bool counter_key__equal(const struct counter_key *a, const struct counter_key *b,
+			       enum evsel_match_mode mode)
 {
+	unsigned int a_priv, b_priv;
+
 	if (a->type != b->type || a->config != b->config ||
 	    a->config1 != b->config1 || a->config2 != b->config2 ||
 	    a->config3 != b->config3 || a->config4 != b->config4)
@@ -729,8 +766,62 @@ static bool counter_key__equal(const struct counter_key *a, const struct counter
 	if (a->pmu_name && b->pmu_name && strcmp(a->pmu_name, b->pmu_name))
 		return false;
 
-	return a->exclude == b->exclude && a->modifiers == b->modifiers &&
-	       a->precise_ip == b->precise_ip && a->cgrp == b->cgrp;
+	if ((a->modifiers ^ b->modifiers) & COUNTER_KEY_MOD_RETIRE_LAT)
+		return false;
+
+	if (mode == EVSEL_MATCH_LIVE) {
+		return a->exclude == b->exclude && a->modifiers == b->modifiers &&
+		       a->precise_ip == b->precise_ip && a->cgrp == b->cgrp;
+	}
+
+	/*
+	 * If the metric event specified privilege exclusion bits (e.g. :k or
+	 * :u), require the recorded event to match. Otherwise allow matching a
+	 * recorded event whose exclude_kernel/exclude_hv bits were set by
+	 * evsel__fallback() when perf_event_paranoid > 1.
+	 */
+	a_priv = a->exclude & COUNTER_KEY_EXCLUDE_PRIV;
+	b_priv = b->exclude & COUNTER_KEY_EXCLUDE_PRIV;
+	if (a_priv != b_priv && (a_priv != 0 || (b_priv & COUNTER_KEY_EXCLUDE_USER) != 0))
+		return false;
+
+	/*
+	 * Guest only events (:G) and host only events (:H) must match, except a
+	 * recorded event may exclude the guest when the metric event doesn't if
+	 * exclude_guest was set by evsel__fallback() on EOPNOTSUPP, such as on
+	 * Apple M1.
+	 */
+	if ((a->exclude ^ b->exclude) & COUNTER_KEY_EXCLUDE_HOST)
+		return false;
+	if ((a->exclude & COUNTER_KEY_EXCLUDE_GUEST) && !(b->exclude & COUNTER_KEY_EXCLUDE_GUEST))
+		return false;
+	if ((a->exclude ^ b->exclude) & COUNTER_KEY_EXCLUDE_IDLE)
+		return false;
+
+	return true;
+}
+
+/* Were any counts read for @evsel, for example, from a perf stat record round? */
+static int evsel__count_score(struct evsel *evsel)
+{
+	int score = 0;
+
+	if (!evsel->counts)
+		return 0;
+
+	for (int cpu_idx = 0; cpu_idx < xyarray__max_x(evsel->counts->values); cpu_idx++) {
+		for (int thread = 0; thread < xyarray__max_y(evsel->counts->values); thread++) {
+			struct perf_counts_values *cnt =
+				perf_counts(evsel->counts, cpu_idx, thread);
+
+			if (cnt->run) {
+				if (cnt->val)
+					return 2;
+				score = 1;
+			}
+		}
+	}
+	return score;
 }
 
 static bool evsel__is_grouped(const struct evsel *evsel)
@@ -769,13 +860,19 @@ static int evsel_matcher__add(struct evsel_matcher *matcher, struct evsel *evsel
  * @matcher: The matcher to initialize.
  * @evlist: The evsels that may be matched, more may be added with
  *          evsel_matcher__add().
+ * @mode: How strictly evsels must agree to be matched.
+ * @match_grouping: Prefer evsels whose group membership is the same as the
+ *                  evsel being matched.
  */
-static int evsel_matcher__init(struct evsel_matcher *matcher, struct evlist *evlist)
+static int evsel_matcher__init(struct evsel_matcher *matcher, struct evlist *evlist,
+			       enum evsel_match_mode mode, bool match_grouping)
 {
 	struct evsel *evsel;
 
 	memset(matcher, 0, sizeof(*matcher));
 	INIT_LIST_HEAD(&matcher->legacy_hw_keys);
+	matcher->mode = mode;
+	matcher->match_grouping = match_grouping;
 	evlist__for_each_entry(evlist, evsel) {
 		int ret = evsel_matcher__add(matcher, evsel);
 
@@ -868,9 +965,10 @@ static void own_evsel__metric_match_info(struct evsel **own, struct evsel **map,
  * @forced_group_leader: Require a group leader whose members all satisfy
  *                       arch_evsel__must_be_in_group().
  *
- * The same counter may be present more than once, such as when the counter is
- * in different groups. Prefer an evsel with the same name and the same group
- * membership.
+ * The same counter may be present more than once, such as when different
+ * metrics were measured in different groups. Prefer an evsel with the same
+ * name, as a metric would have been parsed to, the same exclusion bits, counts
+ * and, if requested, the same group membership.
  */
 static struct evsel *evsel_matcher__find_for_metric(struct evsel_matcher *matcher,
 						    struct evsel *evsel,
@@ -895,19 +993,23 @@ static struct evsel *evsel_matcher__find_for_metric(struct evsel_matcher *matche
 			continue;
 
 		/*
-		 * Don't match an ungrouped event to a member of a group, as the
-		 * group may multiplex differently from the metric's other
-		 * events.
+		 * When measuring live events, don't match an ungrouped event to
+		 * a member of a group, as the group may multiplex differently
+		 * from the metric's other events.
 		 */
-		if (!leader && !forced_group_leader && evsel__is_grouped(pos))
+		if (matcher->mode == EVSEL_MATCH_LIVE && !leader && !forced_group_leader &&
+		    evsel__is_grouped(pos))
 			continue;
 
 		if (forced_group_leader &&
 		    (!evsel__is_group_leader(pos) || !evsel__is_forced_group(pos)))
 			continue;
 
+		if (matcher->mode == EVSEL_MATCH_RECORDED && !pos->supported)
+			continue;
+
 		/* Skip if the evsels don't program the same counter. */
-		if (!counter_key__equal(&key, &entry->key))
+		if (!counter_key__equal(&key, &entry->key, matcher->mode))
 			continue;
 
 		if (!metric_leader_compatible(evsel, metric_sources, mapped_metric_leader, pos))
@@ -918,8 +1020,10 @@ static struct evsel *evsel_matcher__find_for_metric(struct evsel_matcher *matche
 		    !evsel__shares_metric_id(evsel, pos))
 			continue;
 
-		score = (!strcmp(evsel__name(evsel), evsel__name(pos)) ? 2 : 0) +
-			(grouped == evsel__is_grouped(pos) ? 1 : 0);
+		score = (!strcmp(evsel__name(evsel), evsel__name(pos)) ? 16 : 0) +
+			(key.exclude == entry->key.exclude ? 8 : 0) +
+			evsel__count_score(pos) * 2 +
+			(matcher->match_grouping && grouped == evsel__is_grouped(pos) ? 1 : 0);
 		if (score > best_score) {
 			best = pos;
 			best_score = score;
@@ -928,9 +1032,8 @@ static struct evsel *evsel_matcher__find_for_metric(struct evsel_matcher *matche
 	return best;
 }
 
-static __maybe_unused struct evsel *evsel_matcher__find(struct evsel_matcher *matcher,
-							struct evsel *evsel,
-							const struct evsel *leader)
+static struct evsel *evsel_matcher__find(struct evsel_matcher *matcher, struct evsel *evsel,
+					 const struct evsel *leader)
 {
 	return evsel_matcher__find_for_metric(matcher, evsel, leader,
 					      /*metric_sources=*/0,
@@ -986,150 +1089,375 @@ static bool evsel_matcher__find_group(struct evsel_matcher *matcher, struct evse
 }
 
 struct find_matching_metrics_args {
+	/** @evlist: The evlist of the perf.data file. */
 	struct evlist *evlist;
+	/** @matcher: Matcher of evlist's evsels. */
+	struct evsel_matcher matcher;
 	bool system_wide;
 };
 
-static struct evsel *map_metric_evsel_to_evlist(struct evlist *data_evlist,
-						struct evsel *metric_evsel)
+/*
+ * Find the evsel in @metric_evlist counting @ev, or an event merged with it as
+ * its metric leader, on @pmu.
+ */
+static struct evsel *metric_evsel_on_pmu(struct evlist *metric_evlist, struct evsel *ev,
+					 const struct perf_pmu *pmu)
 {
-	struct evsel *data_evsel;
+	struct evsel *pos;
 
-	evlist__for_each_entry(data_evlist, data_evsel) {
-		/* Skip if perf_event_attr differ. */
-		if (metric_evsel->core.attr.type != data_evsel->core.attr.type)
-			continue;
-		if (metric_evsel->core.attr.config != data_evsel->core.attr.config)
-			continue;
-		return data_evsel;
+	if (ev->pmu == pmu)
+		return ev;
+
+	evlist__for_each_entry(metric_evlist, pos) {
+		if (pos->pmu == pmu &&
+		    (pos->metric_leader == ev ||
+		     evsel__shares_metric_id(pos, ev)))
+			return pos;
 	}
-	return NULL;
+	if (ev->pmu && ev->pmu->is_core && pmu && pmu->is_core)
+		return NULL;
+	return ev;
 }
 
-static int find_matching_metrics_cb(const struct pmu_metric *pm,
-				    const struct pmu_metrics_table *table __maybe_unused,
-				    void *data)
+/*
+ * Copy @expr, whose events are in @metric_evlist, replacing each event with the
+ * corresponding event on @pmu and then with the matching evsel of the data
+ * evlist. The metric identifiers of the events are added to the matched data
+ * evsel as different metrics may refer to the same recorded event by different
+ * names.
+ */
+static struct metric_expr *metric_expr__dup_for_data(struct find_matching_metrics_args *args,
+						     struct evlist *metric_evlist,
+						     const struct metric_expr *expr,
+						     const struct perf_pmu *pmu,
+						     struct evsel *target_data_evsel)
 {
-	struct find_matching_metrics_args *args = data;
-	struct evlist *data_evlist = args->evlist;
-	struct evlist *metric_evlist = evlist__new();
-	struct evsel *metric_evsel;
-	int ret = metricgroup__parse_groups(metric_evlist,
-					/*pmu=*/"all",
-					/*cputype_filter=*/false,
-					pm->metric_name,
-					/*metric_no_group=*/false,
-					/*metric_no_merge=*/false,
-					/*metric_no_threshold=*/true,
-					/*user_requested_cpu_list=*/NULL,
-					args->system_wide,
-					/*hardware_aware_grouping=*/false);
+	struct evsel *data_group = evsel__leader(target_data_evsel);
+	struct metric_expr *copy = zalloc(sizeof(*copy));
+	int nr = 0;
 
-	if (ret) {
-		/* Metric parsing failed but continue the search. */
-		goto out;
+	if (data_group->core.nr_members <= 1)
+		data_group = NULL;
+
+	if (!copy)
+		return NULL;
+
+	copy->metric_expr = expr->metric_expr;
+	copy->metric_threshold = expr->metric_threshold;
+	copy->metric_unit = expr->metric_unit;
+	copy->default_metricgroup_name = expr->default_metricgroup_name;
+	copy->runtime = expr->runtime;
+	copy->metric_name = strdup(expr->metric_name);
+	if (!copy->metric_name)
+		goto err;
+
+	if (expr->metric_refs) {
+		int nr_refs = 0;
+
+		while (expr->metric_refs[nr_refs].metric_name)
+			nr_refs++;
+		copy->metric_refs = calloc(nr_refs + 1, sizeof(*copy->metric_refs));
+		if (!copy->metric_refs)
+			goto err;
+		memcpy(copy->metric_refs, expr->metric_refs, nr_refs * sizeof(*copy->metric_refs));
 	}
 
+	while (expr->metric_events[nr])
+		nr++;
+	copy->metric_events = calloc(nr + 1, sizeof(*copy->metric_events));
+	if (!copy->metric_events)
+		goto err;
+
 	/*
-	 * Check the data_evlist has an entry for each metric_evlist entry. If
-	 * the data evsel was already set up avoid changing data that may
-	 * break it.
+	 * Map every event before modifying any data evsel so that a failure
+	 * doesn't leave metric ids or leaders on data evsels of an unused copy.
 	 */
-	evlist__for_each_entry(metric_evlist, metric_evsel) {
-		struct evsel *data_evsel =
-			map_metric_evsel_to_evlist(data_evlist, metric_evsel);
-		struct evsel *new_metric_leader;
+	for (int i = 0; i < nr; i++) {
+		struct evsel *ev = metric_evsel_on_pmu(metric_evlist, expr->metric_events[i], pmu);
+
+		if (!ev)
+			goto err;
+		if (data_group && evsel__is_grouped(ev))
+			copy->metric_events[i] = evsel_matcher__find(&args->matcher, ev,
+								     data_group);
+		if (!copy->metric_events[i])
+			copy->metric_events[i] = evsel_matcher__find(&args->matcher, ev,
+								     /*leader=*/NULL);
+		if (!copy->metric_events[i])
+			goto err;
+	}
+	for (int i = 0, j = 0; i < nr; i++) {
+		struct evsel *data_evsel = copy->metric_events[i];
+		bool dup = false;
 
-		if (!data_evsel) {
-			pr_debug("Skipping metric '%s' as evsel '%s' is missing\n",
-				pm->metric_name, evsel__name(metric_evsel));
-			goto out;
+		if (evsel__copy_metric_ids(data_evsel, expr->metric_events[i]))
+			goto err;
+		/*
+		 * Different metric events may map to the same data evsel, whose
+		 * metric ids now include those of both. Only keep the first so
+		 * that the data evsel's value isn't counted more than once.
+		 */
+		for (int k = 0; k < j; k++) {
+			if (copy->metric_events[k] == data_evsel) {
+				dup = true;
+				break;
+			}
 		}
-
-		if (data_evsel->metric_leader == NULL)
+		copy->metric_events[i] = NULL;
+		if (dup)
 			continue;
+		copy->metric_events[j++] = data_evsel;
+		/*
+		 * As in setup_metric_events(), a matched event without a metric
+		 * leader leads itself so that evsel__source_count() counts it.
+		 */
+		if (!data_evsel->metric_leader)
+			data_evsel->metric_leader = data_evsel;
+	}
+	return copy;
+err:
+	metric_expr__delete(copy);
+	return NULL;
+}
 
-		if (metric_evsel->metric_leader == metric_evsel) {
-			new_metric_leader = data_evsel;
-		} else {
-			new_metric_leader =
-				map_metric_evsel_to_evlist(data_evlist,
-								 metric_evsel->metric_leader);
+static bool metric_evsel__has_pmu_siblings(struct evlist *metric_evlist,
+					   const struct evsel *ev)
+{
+	struct evsel *pos;
+
+	evlist__for_each_entry(metric_evlist, pos) {
+		if (pos != ev && pos->pmu != ev->pmu &&
+		    pos->metric_leader == ev &&
+		    evsel__shares_metric_id(pos, ev))
+			return true;
+	}
+	return false;
+}
+
+/*
+ * Pick the event in @metric_evlist whose per-PMU instances @expr should be
+ * duplicated and displayed for. pick_display_evsel() may select a single-PMU
+ * event, such as software/cpu-clock/ in branch_frequency, when another event in
+ * @expr was expanded across multiple PMUs (e.g. hybrid core PMUs); prefer the
+ * multi-PMU event so @expr is matched for each PMU.
+ */
+static struct evsel *metric_expr__leader_evsel(struct evlist *metric_evlist,
+					       struct evsel *metric_evsel,
+					       const struct metric_expr *expr)
+{
+	if (metric_evsel__has_pmu_siblings(metric_evlist, metric_evsel))
+		return metric_evsel;
+
+	for (int i = 0; expr->metric_events[i]; i++) {
+		struct evsel *ev = expr->metric_events[i];
+
+		if (metric_evsel__has_pmu_siblings(metric_evlist, ev))
+			return ev;
+	}
+	return metric_evsel;
+}
+
+/* Does @metric_events already contain a metric equivalent to @expr? */
+static bool metric_events__contains(struct rblist *metric_events, const struct metric_expr *expr)
+{
+	for (unsigned int i = 0; i < rblist__nr_entries(metric_events); i++) {
+		struct rb_node *nd = rblist__entry(metric_events, i);
+		struct metric_event *me = container_of(nd, struct metric_event, nd);
+		const struct metric_expr *pos;
+
+		list_for_each_entry(pos, &me->head, nd) {
+			int j;
+
+			if (strcmp(pos->metric_name, expr->metric_name))
+				continue;
+			for (j = 0; pos->metric_events[j] && expr->metric_events[j]; j++) {
+				if (pos->metric_events[j] != expr->metric_events[j])
+					break;
+			}
+			if (!pos->metric_events[j] && !expr->metric_events[j])
+				return true;
 		}
-		/* Mismatching evsel leaders. */
-		if (data_evsel->metric_leader != new_metric_leader) {
-			pr_debug("Skipping metric '%s' due to mismatching evsel metric leaders '%s' vs '%s'\n",
-				pm->metric_name, evsel__name(metric_evsel),
-				evsel__name(data_evsel));
-			goto out;
+	}
+	return false;
+}
+
+static int add_matching_metric_evlist(struct find_matching_metrics_args *args,
+				      struct evlist *metric_evlist,
+				      bool default_metricgroup)
+{
+	struct evlist *data_evlist = args->evlist;
+	struct evsel *metric_evsel;
+
+	if (default_metricgroup) {
+		evlist__for_each_entry(metric_evlist, metric_evsel) {
+			if (metric_evsel->default_show_events) {
+				struct evsel *data_evsel =
+					evsel_matcher__find(&args->matcher, metric_evsel,
+							    /*leader=*/NULL);
+
+				if (data_evsel)
+					data_evsel->default_show_events = true;
+			}
 		}
 	}
+
 	/*
-	 * Metric events match those in the data evlist, copy metric evsel
-	 * data into the data evlist.
+	 * Metric expressions are associated with a metric leader event and
+	 * computed for each PMU of the events led by it. Rather than share
+	 * metric leaders between metrics, which may conflict, attach a copy of
+	 * the expression to each such data event using the data events of its
+	 * PMU.
 	 */
 	evlist__for_each_entry(metric_evlist, metric_evsel) {
-		struct evsel *data_evsel =
-			map_metric_evsel_to_evlist(data_evlist, metric_evsel);
 		struct metric_event *metric_me =
 			metricgroup__lookup(evlist__metric_events(metric_evlist),
 					    metric_evsel,
 					    /*create=*/false);
+		struct metric_expr *expr;
 
-		if (evsel__copy_metric_ids(data_evsel, metric_evsel))
-			goto out;
+		if (!metric_me || metric_me->evsel != metric_evsel)
+			continue;
+
+		list_for_each_entry(expr, &metric_me->head, nd) {
+			struct evsel *leader =
+				metric_expr__leader_evsel(metric_evlist, metric_evsel, expr);
+			struct evsel *pos;
+
+			evlist__for_each_entry(metric_evlist, pos) {
+				struct evsel *data_evsel;
+				struct metric_event *data_me;
+				struct metric_expr *copy;
+
+				if (pos != leader &&
+				    (pos->metric_leader != leader ||
+				     !evsel__shares_metric_id(pos, leader)))
+					continue;
 
-		if (data_evsel->metric_leader == NULL) {
-			if (metric_evsel->metric_leader == metric_evsel) {
+				data_evsel = evsel_matcher__find(&args->matcher, pos,
+								 /*leader=*/NULL);
+				if (!data_evsel)
+					continue;
+
+				copy = metric_expr__dup_for_data(args, metric_evlist, expr,
+								 pos->pmu, data_evsel);
+				if (!copy)
+					continue;
+				if (metric_events__contains(evlist__metric_events(data_evlist),
+							    copy)) {
+					metric_expr__delete(copy);
+					continue;
+				}
+				data_me = metricgroup__lookup(evlist__metric_events(data_evlist),
+							      data_evsel, /*create=*/true);
+				if (!data_me) {
+					metric_expr__delete(copy);
+					return -ENOMEM;
+				}
 				data_evsel->metric_leader = data_evsel;
-			} else {
-				data_evsel->metric_leader =
-					map_metric_evsel_to_evlist(data_evlist,
-								       metric_evsel->metric_leader);
+				if (default_metricgroup && metric_me->is_default)
+					data_me->is_default = true;
+				list_add_tail(&copy->nd, &data_me->head);
+				pr_debug("Found metric '%s' whose evsels match those in the perf data\n",
+					 copy->metric_name);
 			}
 		}
+	}
+	return 0;
+}
 
-		if (metric_me) {
-			struct metric_expr *expr;
-			struct metric_event *data_me =
-				metricgroup__lookup(evlist__metric_events(data_evlist),
-						    data_evsel,
-						    /*create=*/true);
-
-			if (!data_me) {
-				/*
-				 * As the metric_expr is created, the only
-				 * failure is a lack of memory.
-				 */
-				goto out;
-			}
-			list_splice_init(&metric_me->head, &data_me->head);
-			list_for_each_entry(expr, &data_me->head, nd) {
-				for (int i = 0; expr->metric_events[i]; i++) {
-					expr->metric_events[i] =
-						map_metric_evsel_to_evlist(data_evlist,
-									expr->metric_events[i]);
-				}
-			}
+static int find_matching_metrics_cb(const struct pmu_metric *pm,
+				    const struct pmu_metrics_table *table __maybe_unused,
+				    void *data)
+{
+	struct find_matching_metrics_args *args = data;
+	struct evlist *metric_evlist = evlist__new();
+	struct evsel *metric_evsel;
+	bool found;
+	int ret;
+
+	if (!metric_evlist)
+		return -ENOMEM;
+
+	evlist__set_session(metric_evlist, evlist__session(args->evlist));
+	/*
+	 * Event grouping doesn't affect the matching and avoids warnings about
+	 * grouping constraints.
+	 */
+	ret = metricgroup__parse_groups(metric_evlist,
+					/*pmu=*/"all",
+					/*cputype_filter=*/false,
+					pm->metric_name,
+					/*metric_no_group=*/true,
+					/*metric_no_merge=*/false,
+					/*metric_no_threshold=*/true,
+					/*user_requested_cpu_list=*/NULL,
+					args->system_wide,
+					/*hardware_aware_grouping=*/false);
+	if (ret) {
+		/* Metric parsing failed but continue the search. */
+		ret = 0;
+		goto out;
+	}
+
+	/*
+	 * Skip metrics none of whose events were recorded. Copies of the
+	 * metric for each PMU, such as on hybrid systems where only one core
+	 * PMU's events may have been recorded, are only added if all their
+	 * events match.
+	 */
+	found = false;
+	evlist__for_each_entry(metric_evlist, metric_evsel) {
+		if (evsel_matcher__find(&args->matcher, metric_evsel, /*leader=*/NULL)) {
+			found = true;
+			break;
 		}
 	}
-	pr_debug("Found metric '%s' whose evsels match those of in the perf data\n",
-		 pm->metric_name);
+	if (!found) {
+		pr_debug("Skipping metric '%s' as no evsels are in the data\n", pm->metric_name);
+		goto out;
+	}
+
+	ret = add_matching_metric_evlist(args, metric_evlist,
+					 /*default_metricgroup=*/false);
 out:
 	evlist__put(metric_evlist);
-	return 0;
+	return ret;
+}
+
+static int find_matching_metrics_args__init(struct find_matching_metrics_args *args,
+					    struct evlist *evlist, bool system_wide,
+					    bool match_grouping)
+{
+	args->evlist = evlist;
+	args->system_wide = system_wide;
+	return evsel_matcher__init(&args->matcher, evlist, EVSEL_MATCH_RECORDED, match_grouping);
+}
+
+static void find_matching_metrics_args__exit(struct find_matching_metrics_args *args)
+{
+	evsel_matcher__exit(&args->matcher);
 }
 
 int metricgroup__add_matching_metrics(struct evlist *evlist, bool system_wide)
 {
 	struct perf_env *env = evlist__env(evlist) ?: host_env();
-	struct find_matching_metrics_args args = {
-		.evlist = evlist,
-		.system_wide = system_wide,
-	};
+	struct find_matching_metrics_args args;
+	int ret;
+
+	/*
+	 * The metrics are parsed without event grouping, so don't prefer
+	 * recorded events by their grouping.
+	 */
+	ret = find_matching_metrics_args__init(&args, evlist, system_wide,
+					       /*match_grouping=*/false);
+	if (ret)
+		goto out;
 
-	return metricgroup__for_each_metric(perf_env__pmus(env), pmu_metrics_table__find(),
-					    find_matching_metrics_cb, &args);
+	ret = metricgroup__for_each_metric(perf_env__pmus(env), pmu_metrics_table__find(),
+					   find_matching_metrics_cb, &args);
+out:
+	find_matching_metrics_args__exit(&args);
+	return ret;
 }
 
 static int append_metric_id_cb(const char *metric_id, void *data)
@@ -2322,7 +2650,8 @@ static int parse_groups(struct evlist *perf_evlist,
 	 * Unless sharing is disabled, metrics use the events of other metrics,
 	 * and those already in perf_evlist, that count the same thing.
 	 */
-	ret = evsel_matcher__init(&matcher, perf_evlist);
+	ret = evsel_matcher__init(&matcher, perf_evlist, EVSEL_MATCH_LIVE,
+				  /*match_grouping=*/true);
 	if (ret)
 		goto out;
 
diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
index 2069211913dc..25398eafd6e2 100644
--- a/tools/perf/util/metricgroup.h
+++ b/tools/perf/util/metricgroup.h
@@ -104,7 +104,10 @@ int metricgroup__for_each_metric(struct perf_pmus *pmus, const struct pmu_metric
  *
  * For every metric whose events all have a corresponding evsel in @evlist,
  * add the metric to the metric events of @evlist so that it is computed and
- * displayed along with the counts of the evsels.
+ * displayed along with the counts of the evsels. Evsels correspond when they
+ * program the same counter on a PMU of the same name, with legacy hardware
+ * events matching the equivalent sysfs event of the PMU. A metric already added
+ * for the same evsels isn't added again.
  *
  * Return: 0 on success or a negative error code.
  */
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 27/30] perf metricgroup: Add metricgroup__add_recorded_metrics()
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (25 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 26/30] perf metricgroup: Improve matching of metrics to recorded events Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:42   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 28/30] perf stat report: Display metrics for recorded events Ian Rogers
                   ` (2 subsequent siblings)
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Add metricgroup__add_matching_metric_groups() and
metricgroup__add_recorded_metrics() to parse metrics or metricgroups
requested on a recorded perf stat command line (or the Default
metricgroups when no events or metrics were explicitly specified) and
match them against the recorded events in the evlist.

When reporting commands such as 'perf stat report' or 'perf script'
inspect a perf.data file recorded by 'perf stat record', this allows
reconstructing the exact metrics requested at record time (including -M,
--transaction, --smi-cost, --topdown, -d, --cputype, and --pmu-filter)
or optionally adding all matching JSON metrics when --all-metrics is
enabled.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/metricgroup.c | 358 ++++++++++++++++++++++++++++++++--
 tools/perf/util/metricgroup.h |  62 ++++++
 2 files changed, 407 insertions(+), 13 deletions(-)

diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index f41e11a9a3e7..0918a3758bce 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -20,6 +20,8 @@
 #include "tool_pmu.h"
 #include "expr.h"
 #include "rblist.h"
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 #include "strlist.h"
@@ -1281,25 +1283,34 @@ static bool metric_events__contains(struct rblist *metric_events, const struct m
 	return false;
 }
 
+static void evsel_matcher__mark_default(struct evsel_matcher *matcher, struct evsel *metric_evsel)
+{
+	struct counter_key key;
+	bool fake = metric_evsel->pmu && perf_pmu__is_fake(metric_evsel->pmu);
+
+	evsel_matcher__key(matcher, metric_evsel, &key);
+	for (size_t i = 0; i < matcher->nr_entries; i++) {
+		struct evsel_matcher_entry *entry = &matcher->entries[i];
+		struct evsel *pos = entry->evsel;
+
+		if (!counter_key__equal(&key, &entry->key, matcher->mode))
+			continue;
+		if ((fake || (pos->pmu && perf_pmu__is_fake(pos->pmu))) &&
+		    !evsel__shares_metric_id(metric_evsel, pos))
+			continue;
+		pos->default_metricgroup = true;
+		if (metric_evsel->default_show_events)
+			pos->default_show_events = true;
+	}
+}
+
 static int add_matching_metric_evlist(struct find_matching_metrics_args *args,
 				      struct evlist *metric_evlist,
 				      bool default_metricgroup)
 {
 	struct evlist *data_evlist = args->evlist;
 	struct evsel *metric_evsel;
-
-	if (default_metricgroup) {
-		evlist__for_each_entry(metric_evlist, metric_evsel) {
-			if (metric_evsel->default_show_events) {
-				struct evsel *data_evsel =
-					evsel_matcher__find(&args->matcher, metric_evsel,
-							    /*leader=*/NULL);
-
-				if (data_evsel)
-					data_evsel->default_show_events = true;
-			}
-		}
-	}
+	bool any_matched = false;
 
 	/*
 	 * Metric expressions are associated with a metric leader event and
@@ -1342,6 +1353,7 @@ static int add_matching_metric_evlist(struct find_matching_metrics_args *args,
 								 pos->pmu, data_evsel);
 				if (!copy)
 					continue;
+				any_matched = true;
 				if (metric_events__contains(evlist__metric_events(data_evlist),
 							    copy)) {
 					metric_expr__delete(copy);
@@ -1362,6 +1374,17 @@ static int add_matching_metric_evlist(struct find_matching_metrics_args *args,
 			}
 		}
 	}
+
+	if (default_metricgroup && any_matched) {
+		/*
+		 * Mark all recorded events counting the same thing as a Default
+		 * metric event, including duplicates across groups, so
+		 * perf_stat__skip_metric_event() hides unused duplicates while
+		 * leaving unrelated events visible.
+		 */
+		evlist__for_each_entry(metric_evlist, metric_evsel)
+			evsel_matcher__mark_default(&args->matcher, metric_evsel);
+	}
 	return 0;
 }
 
@@ -1460,6 +1483,315 @@ int metricgroup__add_matching_metrics(struct evlist *evlist, bool system_wide)
 	return ret;
 }
 
+int metricgroup__add_matching_metric_groups(struct evlist *evlist,
+					    const char *pmu,
+					    bool cputype_filter,
+					    const char *str,
+					    bool metric_no_group,
+					    bool metric_no_merge,
+					    bool metric_no_threshold,
+					    bool system_wide,
+					    bool default_metricgroup)
+{
+	struct find_matching_metrics_args args;
+	struct evlist *metric_evlist = NULL;
+	int ret;
+
+	ret = find_matching_metrics_args__init(&args, evlist, system_wide,
+					       /*match_grouping=*/!metric_no_group);
+	if (ret)
+		goto out;
+
+	metric_evlist = evlist__new();
+	if (!metric_evlist) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	evlist__set_session(metric_evlist, evlist__session(evlist));
+
+	ret = metricgroup__parse_groups(metric_evlist,
+					pmu ?: "all",
+					cputype_filter,
+					str,
+					metric_no_group,
+					metric_no_merge,
+					metric_no_threshold,
+					/*user_requested_cpu_list=*/NULL,
+					system_wide,
+					/*hardware_aware_grouping=*/false);
+	if (!ret) {
+		ret = add_matching_metric_evlist(&args, metric_evlist,
+						 default_metricgroup);
+	}
+
+out:
+	evlist__put(metric_evlist);
+	find_matching_metrics_args__exit(&args);
+	return ret;
+}
+
+static void append_recorded_metric_str(char **rec_metrics, const char *m_arg)
+{
+	char *tmp = NULL;
+
+	if (!m_arg)
+		return;
+	if (!*rec_metrics) {
+		*rec_metrics = strdup(m_arg);
+	} else if (asprintf(&tmp, "%s,%s", *rec_metrics, m_arg) >= 0) {
+		free(*rec_metrics);
+		*rec_metrics = tmp;
+	}
+}
+
+void metricgroup__add_recorded_metrics(struct evlist *evlist,
+				       const char *metrics,
+				       const char *pmu_filter,
+				       bool cputype_filter,
+				       bool metric_no_group,
+				       bool metric_no_merge,
+				       bool metric_no_threshold,
+				       bool all_metrics,
+				       bool system_wide,
+				       bool default_metricgroup,
+				       bool *metric_only)
+{
+	struct perf_env *env = evlist__env(evlist) ?: host_env();
+	struct perf_pmus *pmus = perf_env__pmus(env);
+	const char **argv = env->cmdline_argv;
+	int argc = env->nr_cmdline;
+	char *rec_metrics = NULL;
+	bool rec_transaction = false, rec_smi = false, rec_topdown = false;
+	bool rec_null = false, has_events = false, had_rec_metrics = false;
+	bool rec_metric_only = false, rec_force_metric_only = false;
+	bool rec_no_group = metric_no_group;
+	bool rec_no_merge = metric_no_merge;
+	bool rec_no_threshold = metric_no_threshold;
+	bool seen_record = false;
+	unsigned int rec_td_level = 0;
+	int rec_detailed = 0;
+
+	if (metrics) {
+		metricgroup__add_matching_metric_groups(evlist, pmu_filter,
+							cputype_filter, metrics,
+							metric_no_group,
+							metric_no_merge,
+							metric_no_threshold,
+							system_wide,
+							/*default_metricgroup=*/false);
+		if (all_metrics)
+			metricgroup__add_matching_metrics(evlist, system_wide);
+		return;
+	}
+
+	if (!argv || argc < 2 || strcmp(argv[1], "stat")) {
+		if (all_metrics)
+			metricgroup__add_matching_metrics(evlist, system_wide);
+		return;
+	}
+
+	for (int i = 2; i < argc && argv[i]; i++) {
+		const char *arg = argv[i];
+
+		if (!strcmp(arg, "--"))
+			break;
+
+		if (arg[0] != '-') {
+			if (!seen_record && strlen(arg) > 2 && strstarts("record", arg)) {
+				seen_record = true;
+				continue;
+			}
+			break;
+		}
+
+		if (arg[1] != '-') {
+			for (const char *p = arg + 1; *p; p++) {
+				if (*p == 'd') {
+					rec_detailed++;
+				} else if (*p == 'T') {
+					rec_transaction = true;
+				} else if (*p == 'n') {
+					rec_null = true;
+				} else if (*p == 'e') {
+					has_events = true;
+					if (!p[1])
+						i++;
+					break;
+				} else if (*p == 'M') {
+					const char *m_arg = p[1] ? p + 1 : NULL;
+
+					if (!m_arg && i + 1 < argc)
+						m_arg = argv[++i];
+					append_recorded_metric_str(&rec_metrics, m_arg);
+					break;
+				} else if (strchr("ptbrCxGoID", *p)) {
+					if (!p[1])
+						i++;
+					break;
+				}
+			}
+			continue;
+		}
+
+		if (!strcmp(arg, "--metrics") || strstarts(arg, "--metrics=")) {
+			const char *m_arg = arg[9] == '=' ? arg + 10
+							  : (i + 1 < argc ? argv[++i] : NULL);
+
+			append_recorded_metric_str(&rec_metrics, m_arg);
+		} else if (!strcmp(arg, "--transaction")) {
+			rec_transaction = true;
+		} else if (!strcmp(arg, "--smi-cost")) {
+			rec_smi = true;
+		} else if (!strcmp(arg, "--topdown")) {
+			rec_topdown = true;
+		} else if (!strcmp(arg, "--td-level") || strstarts(arg, "--td-level=")) {
+			const char *td_arg = arg[10] == '=' ? arg + 11
+							    : (i + 1 < argc ? argv[++i] : NULL);
+
+			if (td_arg)
+				rec_td_level = strtoul(td_arg, NULL, 10);
+		} else if (!strcmp(arg, "--detailed")) {
+			rec_detailed++;
+		} else if (!strcmp(arg, "--null")) {
+			rec_null = true;
+		} else if (!strcmp(arg, "--metric-only")) {
+			rec_metric_only = true;
+			rec_force_metric_only = true;
+		} else if (!strcmp(arg, "--no-metric-only")) {
+			rec_metric_only = false;
+			rec_force_metric_only = true;
+		} else if (!strcmp(arg, "--metric-no-group")) {
+			rec_no_group = true;
+		} else if (!strcmp(arg, "--no-metric-no-group")) {
+			rec_no_group = false;
+		} else if (!strcmp(arg, "--metric-no-merge")) {
+			rec_no_merge = true;
+		} else if (!strcmp(arg, "--no-metric-no-merge")) {
+			rec_no_merge = false;
+		} else if (!strcmp(arg, "--metric-no-threshold")) {
+			rec_no_threshold = true;
+		} else if (!strcmp(arg, "--no-metric-no-threshold")) {
+			rec_no_threshold = false;
+		} else if (!strcmp(arg, "--cputype") || strstarts(arg, "--cputype=")) {
+			const char *cpu_arg = arg[9] == '=' ? arg + 10
+							    : (i + 1 < argc ? argv[++i] : NULL);
+
+			if (cpu_arg) {
+				const struct perf_pmu *pmu =
+					perf_pmus__pmu_for_pmu_filter(pmus, cpu_arg);
+
+				pmu_filter = pmu ? pmu->name : cpu_arg;
+				cputype_filter = true;
+			}
+		} else if (!strcmp(arg, "--pmu-filter") || strstarts(arg, "--pmu-filter=")) {
+			pmu_filter = arg[12] == '=' ? arg + 13
+						    : (i + 1 < argc ? argv[++i] : NULL);
+			cputype_filter = false;
+		} else if (!strcmp(arg, "--event") || strstarts(arg, "--event=") ||
+			   !strcmp(arg, "--pfm-events") || strstarts(arg, "--pfm-events=")) {
+			has_events = true;
+			if (!strchr(arg, '='))
+				i++;
+		} else if (!strcmp(arg, "--iostat") || strstarts(arg, "--iostat=")) {
+			has_events = true;
+		} else if (!strchr(arg, '=') &&
+			   (!strcmp(arg, "--filter") || !strcmp(arg, "--pid") ||
+			    !strcmp(arg, "--tid") || !strcmp(arg, "--bpf-prog") ||
+			    !strcmp(arg, "--bpf-attr-map") || !strcmp(arg, "--repeat") ||
+			    !strcmp(arg, "--cpu") || !strcmp(arg, "--field-separator") ||
+			    !strcmp(arg, "--cgroup") || !strcmp(arg, "--for-each-cgroup") ||
+			    !strcmp(arg, "--output") || !strcmp(arg, "--log-fd") ||
+			    !strcmp(arg, "--pre") || !strcmp(arg, "--post") ||
+			    !strcmp(arg, "--interval-print") || !strcmp(arg, "--interval-count") ||
+			    !strcmp(arg, "--timeout") || !strcmp(arg, "--delay") ||
+			    !strcmp(arg, "--tpebs-mode") || !strcmp(arg, "--control"))) {
+			i++;
+		}
+	}
+
+	if (rec_force_metric_only && metric_only)
+		*metric_only = rec_metric_only;
+
+	if (rec_metrics) {
+		had_rec_metrics = true;
+		if (!all_metrics) {
+			metricgroup__add_matching_metric_groups(evlist, pmu_filter,
+								cputype_filter, rec_metrics,
+								rec_no_group, rec_no_merge,
+								rec_no_threshold, system_wide,
+								/*default_metricgroup=*/false);
+		}
+		zfree(&rec_metrics);
+	}
+
+	if (rec_transaction) {
+		if (!all_metrics) {
+			metricgroup__add_matching_metric_groups(evlist, pmu_filter,
+								cputype_filter, "transaction",
+								rec_no_group, rec_no_merge,
+								rec_no_threshold, system_wide,
+								/*default_metricgroup=*/false);
+		}
+	} else if (rec_smi) {
+		if (metric_only && !rec_force_metric_only && !all_metrics)
+			*metric_only = true;
+		if (!all_metrics) {
+			metricgroup__add_matching_metric_groups(evlist, pmu_filter,
+								cputype_filter, "smi",
+								rec_no_group, rec_no_merge,
+								rec_no_threshold, system_wide,
+								/*default_metricgroup=*/false);
+		}
+	} else if (rec_topdown) {
+		char str[] = "TopdownL1";
+
+		if (metric_only && !rec_force_metric_only && !all_metrics)
+			*metric_only = true;
+		if (!rec_td_level)
+			rec_td_level = 1;
+		str[8] = rec_td_level + '0';
+		if (!all_metrics) {
+			metricgroup__add_matching_metric_groups(evlist, pmu_filter,
+								cputype_filter, str,
+								/*metric_no_group=*/false,
+								/*metric_no_merge=*/false,
+								/*metric_no_threshold=*/true,
+								system_wide,
+								/*default_metricgroup=*/false);
+		}
+	} else if (!has_events && !had_rec_metrics && !rec_null) {
+		static const char * const default_names[] = {
+			"Default", "Default2", "Default3", "Default4",
+		};
+		struct evsel *evsel;
+
+		if (default_metricgroup) {
+			evlist__for_each_entry(evlist, evsel) {
+				if (!evsel__match(evsel, HARDWARE, HW_CPU_CYCLES))
+					evsel->skippable = true;
+			}
+		}
+		for (size_t i = 0; !all_metrics && i < ARRAY_SIZE(default_names); i++) {
+			if ((int)i > rec_detailed)
+				break;
+			if (!metricgroup__has_metric_or_groups(pmus, pmu_filter ?: "all",
+							       default_names[i]))
+				continue;
+			metricgroup__add_matching_metric_groups(evlist, pmu_filter,
+								cputype_filter,
+								default_names[i],
+								/*metric_no_group=*/false,
+								/*metric_no_merge=*/false,
+								/*metric_no_threshold=*/true,
+								system_wide,
+								default_metricgroup);
+		}
+	}
+
+	if (all_metrics)
+		metricgroup__add_matching_metrics(evlist, system_wide);
+}
+
 static int append_metric_id_cb(const char *metric_id, void *data)
 {
 	struct strbuf *sb = data;
diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
index 25398eafd6e2..20fb4ee3cacc 100644
--- a/tools/perf/util/metricgroup.h
+++ b/tools/perf/util/metricgroup.h
@@ -112,6 +112,68 @@ int metricgroup__for_each_metric(struct perf_pmus *pmus, const struct pmu_metric
  * Return: 0 on success or a negative error code.
  */
 int metricgroup__add_matching_metrics(struct evlist *evlist, bool system_wide);
+/**
+ * metricgroup__add_matching_metric_groups() - Parse @str and attach metrics
+ *                                             whose events match @evlist.
+ * @evlist: The recorded evlist to attach matching metric expressions to.
+ * @pmu: PMU filter name or "all".
+ * @cputype_filter: Was @pmu specified via --cputype?
+ * @str: Metric or metricgroup names to parse.
+ * @metric_no_group: Do not group metric events.
+ * @metric_no_merge: Do not merge shared metric events.
+ * @metric_no_threshold: Ignore metric threshold expressions.
+ * @system_wide: Were the events recorded system-wide?
+ * @default_metricgroup: Mark matched events with Default metricgroup display flags.
+ *
+ * Return: 0 on success or a negative error code.
+ */
+int metricgroup__add_matching_metric_groups(struct evlist *evlist,
+					    const char *pmu,
+					    bool cputype_filter,
+					    const char *str,
+					    bool metric_no_group,
+					    bool metric_no_merge,
+					    bool metric_no_threshold,
+					    bool system_wide,
+					    bool default_metricgroup);
+/**
+ * metricgroup__add_recorded_metrics() - Add metrics for a recorded perf.data
+ *                                       evlist.
+ * @evlist: The recorded evlist to attach metrics to.
+ * @metrics: Explicit metric names requested by the reporting command, or NULL
+ *           to inspect the recorded perf stat command line in perf_env.
+ * @pmu_filter: Optional PMU filter string.
+ * @cputype_filter: Was @pmu_filter specified via --cputype?
+ * @metric_no_group: Default metric_no_group setting.
+ * @metric_no_merge: Default metric_no_merge setting.
+ * @metric_no_threshold: Default metric_no_threshold setting.
+ * @all_metrics: Also add all JSON metrics whose events match @evlist.
+ * @system_wide: Were the events recorded system-wide?
+ * @default_metricgroup: Enable Default metricgroup header/skip display flags.
+ * @metric_only: Optional pointer updated if the recorded command line forced
+ *               --metric-only or --no-metric-only.
+ */
+void metricgroup__add_recorded_metrics(struct evlist *evlist,
+				       const char *metrics,
+				       const char *pmu_filter,
+				       bool cputype_filter,
+				       bool metric_no_group,
+				       bool metric_no_merge,
+				       bool metric_no_threshold,
+				       bool all_metrics,
+				       bool system_wide,
+				       bool default_metricgroup,
+				       bool *metric_only);
+/**
+ * metricgroup__has_metric_or_groups() - Check if a metric or metricgroup
+ *                                       named @metric_or_groups exists.
+ * @pmus: The PMUs to search.
+ * @pmu: PMU filter name or "all".
+ * @metric_or_groups: A metric or metricgroup name.
+ *
+ * Return: true if a metric on @pmu is named @metric_or_groups or is in the
+ * metricgroup @metric_or_groups.
+ */
 bool metricgroup__has_metric_or_groups(struct perf_pmus *pmus, const char *pmu,
 				       const char *metric_or_groups);
 unsigned int metricgroups__topdown_max_level(void);
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 28/30] perf stat report: Display metrics for recorded events
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (26 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 27/30] perf metricgroup: Add metricgroup__add_recorded_metrics() Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:44   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 29/30] perf script: Display metrics for perf stat record data Ian Rogers
  2026-11-13 19:53 ` [PATCH v2 30/30] perf test: Add stat record metrics test Ian Rogers
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

perf stat report displays the recorded counts but not the metrics that
perf stat record displayed, as metrics aren't recorded in the perf.data
file. Inspect the recorded command line in struct perf_env and display
the metrics requested on the perf stat record command line (or the
default metrics when no events or metrics were explicitly specified),
for example:

  $ perf stat record -M insn_per_cycle -a sleep 1
  $ perf stat report
  ...
    784,599,279  cpu_atom/cpu-cycles/   # 0.7 instructions  insn_per_cycle
    586,070,692  cpu_atom/instructions/
    776,737,361  cpu_core/cpu-cycles/   # 1.3 instructions  insn_per_cycle
  1,011,699,468  cpu_core/instructions/

    1.004467030 seconds time elapsed

Also add --all-metrics (and the stat.all-metrics config option) to
display all metrics whose events were recorded in the perf.data file,
as well as -M/--metrics and --metric-only options to perf stat report.
Set stat_config.system_wide from the recorded thread map as metrics
such as CPUs_utilized depend upon it.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/Documentation/perf-config.txt |  7 +++
 tools/perf/Documentation/perf-stat.txt   |  5 +++
 tools/perf/builtin-stat.c                | 54 +++++++++++++++++++++---
 tools/perf/util/config.c                 |  3 ++
 tools/perf/util/stat.h                   |  1 +
 5 files changed, 65 insertions(+), 5 deletions(-)

diff --git a/tools/perf/Documentation/perf-config.txt b/tools/perf/Documentation/perf-config.txt
index 6cff6f26af8d..906e59c7e9d5 100644
--- a/tools/perf/Documentation/perf-config.txt
+++ b/tools/perf/Documentation/perf-config.txt
@@ -713,6 +713,13 @@ stat.*::
 		(boolean) Change the default for "--big-num". To make
 		"--no-big-num" the default, set "stat.big-num=false".
 
+	stat.all-metrics::
+		(boolean) Change the default for "--all-metrics" in
+		"perf stat report" and "perf script". When true, all metrics
+		whose events were recorded in the data file are displayed rather
+		than only the metrics requested on the "perf stat record"
+		command line.
+
 intel-pt.*::
 
 	intel-pt.cache-divisor::
diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentation/perf-stat.txt
index 8ea17da89976..7258ec280770 100644
--- a/tools/perf/Documentation/perf-stat.txt
+++ b/tools/perf/Documentation/perf-stat.txt
@@ -426,6 +426,11 @@ information at the boundary of the level 3 cache in the system.
 --per-core::
 Aggregate counts per physical processor for system-wide mode measurements.
 
+--all-metrics::
+Display all metrics that match the recorded events in the perf data file,
+rather than only the metrics requested on the 'perf stat record' command line.
+Default setting can be changed with "perf config stat.all-metrics=true".
+
 -M::
 --metrics::
 Print metrics or metricgroups specified in a comma separated list.
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index 2b563382cf43..64883e7cb6aa 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -2246,6 +2246,29 @@ static int process_stat_round_event(const struct perf_tool *tool __maybe_unused,
 	struct perf_env *env = perf_session__env(session);
 	const char **argv = env->cmdline_argv;
 	int argc = env->nr_cmdline;
+	static bool metrics_added;
+
+	if (!metrics_added) {
+		struct perf_thread_map *threads = evlist__core(session->evlist)->threads;
+		bool system_wide = !threads || perf_thread_map__nr(threads) < 1 ||
+				   perf_thread_map__pid(threads, /*idx=*/0) == -1;
+
+		/* Metrics may use system_wide, such as for #target_cpu. */
+		stat_config.system_wide = system_wide;
+		metricgroup__add_recorded_metrics(session->evlist, metrics,
+						  parse_events_option_args.pmu_filter,
+						  parse_events_option_args.cputype_filter,
+						  stat_config.metric_no_group,
+						  stat_config.metric_no_merge,
+						  stat_config.metric_no_threshold,
+						  stat_config.all_metrics,
+						  system_wide,
+						  /*default_metricgroup=*/true,
+						  force_metric_only ? NULL
+								    : &stat_config.metric_only);
+		zfree(&metrics);
+		metrics_added = true;
+	}
 
 	process_counters();
 
@@ -2296,6 +2319,8 @@ int process_stat_config_event(const struct perf_tool *tool,
 
 static int set_maps(struct perf_stat *st)
 {
+	struct evsel *counter;
+
 	if (!st->cpus || !st->threads)
 		return 0;
 
@@ -2307,6 +2332,9 @@ static int set_maps(struct perf_stat *st)
 	if (evlist__alloc_stats(&stat_config, evsel_list, /*alloc_raw=*/true))
 		return -ENOMEM;
 
+	evlist__for_each_entry(evsel_list, counter)
+		counter->supported = false;
+
 	st->maps_allocated = true;
 	return 0;
 }
@@ -2365,7 +2393,7 @@ static int __cmd_report(int argc, const char **argv)
 {
 	struct perf_session *session;
 	struct opt_aggr_mode opt_mode = {};
-	const struct option options[] = {
+	struct option options[] = {
 	OPT_STRING('i', "input", &input_name, "file", "input file name"),
 	OPT_BOOLEAN(0, "per-thread", &opt_mode.thread, "aggregate counts per thread"),
 	OPT_BOOLEAN(0, "per-socket", &opt_mode.socket,
@@ -2381,11 +2409,20 @@ static int __cmd_report(int argc, const char **argv)
 	OPT_BOOLEAN(0, "per-node", &opt_mode.node, "aggregate counts per numa node"),
 	OPT_BOOLEAN('A', "no-aggr", &opt_mode.no_aggr,
 		    "disable aggregation across CPUs or PMUs"),
+	OPT_CALLBACK('M', "metrics", &evsel_list, "metric/metric group list",
+		     "display specified metrics or metric groups (separated by ,)",
+		     append_metric_groups),
+	OPT_BOOLEAN(0, "all-metrics", &stat_config.all_metrics,
+		    "display all metrics matching recorded events"),
+	OPT_CALLBACK_NOOPT(0, "metric-only", &stat_config.metric_only, NULL,
+			   "Only print computed metrics. No raw values",
+			   enable_metric_only),
 	OPT_END()
 	};
 	struct stat st;
 	int ret;
 
+	set_option_flag(options, 'M', "metrics", PARSE_OPT_NONEG);
 	argc = parse_options(argc, argv, options, stat_report_usage, 0);
 
 	perf_stat.aggr_mode = opt_aggr_mode_to_aggr_mode(&opt_mode);
@@ -2412,8 +2449,10 @@ static int __cmd_report(int argc, const char **argv)
 	perf_stat.tool.stat_round	= process_stat_round_event;
 
 	session = perf_session__new(&perf_stat.data, &perf_stat.tool);
-	if (IS_ERR(session))
-		return PTR_ERR(session);
+	if (IS_ERR(session)) {
+		ret = PTR_ERR(session);
+		goto out;
+	}
 
 	perf_stat.session  = session;
 	stat_config.output = stderr;
@@ -2422,10 +2461,13 @@ static int __cmd_report(int argc, const char **argv)
 
 	ret = perf_session__process_events(session);
 	if (ret)
-		return ret;
+		goto out;
 
 	perf_session__delete(session);
-	return 0;
+out:
+	/* Freed when processing the first stat round, if there was one. */
+	zfree(&metrics);
+	return ret;
 }
 
 static void setup_system_wide(int forks)
@@ -2603,6 +2645,8 @@ int cmd_stat(int argc, const char **argv)
 		OPT_CALLBACK('M', "metrics", &evsel_list, "metric/metric group list",
 			"monitor specified metrics or metric groups (separated by ,)",
 			append_metric_groups),
+		OPT_BOOLEAN(0, "all-metrics", &stat_config.all_metrics,
+			"display all metrics matching recorded events (report only)"),
 		OPT_BOOLEAN_FLAG(0, "all-kernel", &stat_config.all_kernel,
 				"Configure all used events to run in kernel space.",
 				PARSE_OPT_EXCLUSIVE),
diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c
index 287401f159e6..d64d0db51d8e 100644
--- a/tools/perf/util/config.c
+++ b/tools/perf/util/config.c
@@ -514,6 +514,9 @@ static int perf_stat_config(const char *var, const char *value)
 	if (!strcmp(var, "stat.bpf-counter-events"))
 		evsel__bpf_counter_events = strdup(value);
 
+	if (!strcmp(var, "stat.all-metrics"))
+		stat_config.all_metrics = perf_config_bool(var, value);
+
 	/* Add other config variables here. */
 	return 0;
 }
diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h
index e3598037a6aa..a957479804c1 100644
--- a/tools/perf/util/stat.h
+++ b/tools/perf/util/stat.h
@@ -82,6 +82,7 @@ struct perf_stat_config {
 	bool			 metric_no_group;
 	bool			 metric_no_merge;
 	bool			 metric_no_threshold;
+	bool			 all_metrics;
 	bool			 hardware_aware_grouping;
 	bool			 stop_read_counter;
 	bool			 iostat_run;
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 29/30] perf script: Display metrics for perf stat record data
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (27 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 28/30] perf stat report: Display metrics for recorded events Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:47   ` sashiko-bot
  2026-11-13 19:53 ` [PATCH v2 30/30] perf test: Add stat record metrics test Ian Rogers
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

perf script displays the counts of each stat round read from a perf stat
record perf.data file. Also display, after each round, the metrics
requested on the perf stat record command line (or all matching metrics
when --all-metrics or stat.all-metrics=true is used), computed from the
counts aggregated across CPUs and threads, for example:

  $ perf stat record -M insn_per_cycle -a sleep 1
  $ perf script
  CPU THREAD         VAL         ENA         RUN        TIME EVENT
  ...
  cpu_atom/cpu-cycles/  metric:      0.7 instructions  insn_per_cycle
  cpu_core/cpu-cycles/  metric:      1.3 instructions  insn_per_cycle

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/Documentation/perf-script.txt |   6 ++
 tools/perf/builtin-script.c              | 107 ++++++++++++++++++++++-
 2 files changed, 110 insertions(+), 3 deletions(-)

diff --git a/tools/perf/Documentation/perf-script.txt b/tools/perf/Documentation/perf-script.txt
index f0228e784ced..bfd69d411996 100644
--- a/tools/perf/Documentation/perf-script.txt
+++ b/tools/perf/Documentation/perf-script.txt
@@ -468,6 +468,12 @@ include::itrace.txt[]
 	default behavior.  If you want to see separate CALLCHAIN_DEFERRED
 	records for some reason, use --no-merge-callchains explicitly.
 
+--all-metrics::
+	Display all metrics that match the recorded stat events in the perf data
+	file, rather than only the metrics requested on the 'perf stat record'
+	command line. Default setting can be changed with
+	"perf config stat.all-metrics=true".
+
 :GMEXAMPLECMD: script
 :GMEXAMPLESUBCMD:
 include::guest-files.txt[]
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index afed2353ef80..135ec34494bd 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2334,8 +2334,13 @@ static void script_print_metric(struct perf_stat_config *config __maybe_unused,
 
 	if (!fmt)
 		return;
-	perf_sample__fprintf_start(NULL, mctx->sample, mctx->thread, mctx->evsel,
-				   PERF_RECORD_SAMPLE, mctx->fp);
+	if (mctx->sample) {
+		perf_sample__fprintf_start(NULL, mctx->sample, mctx->thread, mctx->evsel,
+					   PERF_RECORD_SAMPLE, mctx->fp);
+	} else {
+		/* Metric computed from the counts of a perf stat record round. */
+		fputs(evsel__name(mctx->evsel), mctx->fp);
+	}
 	fputs("\tmetric: ", mctx->fp);
 	if (color)
 		color_fprintf(mctx->fp, color, fmt, val);
@@ -3795,26 +3800,112 @@ static void script__setup_sample_type(struct perf_script *script)
 	}
 }
 
+/*
+ * Set up the computation of metrics whose events were all recorded by perf stat
+ * record. Counts are aggregated globally for the metrics.
+ */
+static bool script_stat_metrics_init(struct evlist *evlist)
+{
+	struct perf_thread_map *threads = evlist__core(evlist)->threads;
+	struct evsel *counter;
+
+	evlist__for_each_entry(evlist, counter) {
+		if (!counter->stats)
+			return false;
+	}
+	/* Metrics may use system_wide, such as for #target_cpu. */
+	stat_config.system_wide = !threads || perf_thread_map__nr(threads) < 1 ||
+				  perf_thread_map__pid(threads, /*idx=*/0) == -1;
+	metricgroup__add_recorded_metrics(evlist, /*metrics=*/NULL,
+					  /*pmu_filter=*/NULL,
+					  /*cputype_filter=*/false,
+					  stat_config.metric_no_group,
+					  stat_config.metric_no_merge,
+					  stat_config.metric_no_threshold,
+					  stat_config.all_metrics,
+					  stat_config.system_wide,
+					  /*default_metricgroup=*/false,
+					  /*metric_only=*/NULL);
+
+	if (rblist__empty(evlist__metric_events(evlist)))
+		return false;
+
+	if (!stat_config.aggr_map) {
+		stat_config.aggr_get_id = script_aggr_cpu_id_get;
+		stat_config.aggr_map =
+			cpu_aggr_map__new(evlist__core(evlist)->user_requested_cpus,
+					  aggr_cpu_id__global, /*data=*/NULL,
+					  /*needs_sort=*/false);
+		if (!stat_config.aggr_map)
+			return false;
+	}
+	/*
+	 * The aggregated stats may already have been allocated, with the
+	 * aggr_map, for metrics computed from samples.
+	 */
+	if (!evlist__first(evlist)->stats->aggr &&
+	    evlist__alloc_aggr_stats(evlist, stat_config.aggr_map->nr))
+		return false;
+
+	if (!stat_config.output)
+		stat_config.output = stdout;
+	stat_config.aggr_mode = AGGR_GLOBAL;
+	return true;
+}
+
+static void script_print_stat_metrics(struct evlist *evlist)
+{
+	struct evsel *counter;
+	int aggr_idx;
+
+	evlist__for_each_entry(evlist, counter) {
+		struct perf_stat_output_ctx ctx = {
+			.print_metric = script_print_metric,
+			.new_line = script_new_line,
+			.ctx = &(struct metric_ctx) {
+				.evsel = counter,
+				.fp = stdout,
+			},
+			.force_header = false,
+		};
+
+		cpu_aggr_map__for_each_idx(aggr_idx, stat_config.aggr_map)
+			perf_stat__print_shadow_stats(&stat_config, counter, aggr_idx, &ctx);
+	}
+}
+
 static int process_stat_round_event(const struct perf_tool *tool __maybe_unused,
 				    struct perf_session *session,
 				    union perf_event *event)
 {
 	struct perf_record_stat_round *round = &event->stat_round;
 	struct evsel *counter;
+	static int show_metrics = -1;
+
+	if (show_metrics < 0)
+		show_metrics = script_stat_metrics_init(session->evlist) ? 1 : 0;
+
+	if (show_metrics)
+		evlist__reset_aggr_stats(session->evlist);
 
 	evlist__for_each_entry(session->evlist, counter) {
 		perf_stat_process_counter(&stat_config, counter);
 		process_stat(counter, round->time);
 	}
 
+	if (show_metrics)
+		script_print_stat_metrics(session->evlist);
+
 	process_stat_interval(round->time);
 	return 0;
 }
 
 static int process_stat_config_event(const struct perf_tool *tool __maybe_unused,
-				     struct perf_session *session __maybe_unused,
+				     struct perf_session *session,
 				     union perf_event *event)
 {
+	struct evsel *counter;
+
 	perf_event__read_stat_config(&stat_config, &event->stat_config);
 
 	/*
@@ -3823,6 +3914,13 @@ static int process_stat_config_event(const struct perf_tool *tool __maybe_unused
 	 */
 	stat_config.aggr_mode = AGGR_NONE;
 
+	/*
+	 * Mark counters unsupported until a PERF_RECORD_STAT event is
+	 * processed for them by perf_event__process_stat_event().
+	 */
+	evlist__for_each_entry(session->evlist, counter)
+		counter->supported = false;
+
 	return 0;
 }
 
@@ -4206,6 +4304,8 @@ int cmd_script(int argc, const char **argv)
 		    "Enable LBR callgraph stitching approach"),
 	OPT_BOOLEAN('\0', "merge-callchains", &merge_deferred_callchains,
 		    "Enable merge deferred user callchains"),
+	OPT_BOOLEAN(0, "all-metrics", &stat_config.all_metrics,
+		    "Display all metrics matching recorded stat events"),
 	OPTS_EVSWITCH(&script.evswitch),
 	OPT_END()
 	};
@@ -4577,6 +4677,7 @@ int cmd_script(int argc, const char **argv)
 
 	zstd_fini(&(session->zstd_data));
 	evlist__free_stats(session->evlist);
+	zfree(&stat_config.aggr_map);
 	perf_session__delete(session);
 	perf_script__exit(&script);
 
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* [PATCH v2 30/30] perf test: Add stat record metrics test
  2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
                   ` (28 preceding siblings ...)
  2026-11-13 19:53 ` [PATCH v2 29/30] perf script: Display metrics for perf stat record data Ian Rogers
@ 2026-11-13 19:53 ` Ian Rogers
  2026-11-13 20:14   ` sashiko-bot
  29 siblings, 1 reply; 61+ messages in thread
From: Ian Rogers @ 2026-11-13 19:53 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel

Test that metrics measured by perf stat record are displayed by perf
stat report and perf script, and that events recorded with -e only
display matching JSON metrics in perf stat report and perf script when
--all-metrics, stat.all-metrics=true, or -M is used.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/tests/shell/stat.sh | 133 +++++++++++++++++++++++++++++++++
 1 file changed, 133 insertions(+)

diff --git a/tools/perf/tests/shell/stat.sh b/tools/perf/tests/shell/stat.sh
index ac9e031831da..2e1d5978d55a 100755
--- a/tools/perf/tests/shell/stat.sh
+++ b/tools/perf/tests/shell/stat.sh
@@ -6,9 +6,25 @@ set -e
 
 err=0
 stat_output=$(mktemp /tmp/perf-stat-test-output.XXXXX)
+stat_data=""
+stat_config=""
+
+stat_record_metrics_cleanup() {
+  if [ -n "${stat_data}" ]
+  then
+    rm -f "${stat_data}" "${stat_data}.old"
+    stat_data=""
+  fi
+  if [ -n "${stat_config}" ]
+  then
+    rm -f "${stat_config}"
+    stat_config=""
+  fi
+}
 
 cleanup() {
   rm -f "${stat_output}"
+  stat_record_metrics_cleanup
   trap - EXIT TERM INT
 }
 
@@ -92,6 +108,122 @@ test_stat_record_script() {
   echo "stat record and script test [Success]"
 }
 
+test_stat_record_metrics() {
+  echo "stat record metrics test"
+  if ! perf stat -M insn_per_cycle true 2>&1 | grep -E -q "[0-9]+.*[[:space:]]+insn_per_cycle"
+  then
+    echo "stat record metrics test [Skipped insn_per_cycle metric unsupported]"
+    return
+  fi
+  stat_data=$(mktemp /tmp/perf-stat-test-data.XXXXX)
+  if ! perf stat record -o "${stat_data}" -M insn_per_cycle true > /dev/null 2>&1
+  then
+    echo "stat record metrics test [Failed record]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if ! perf stat report -i "${stat_data}" 2>&1 | grep -q "insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed missing metric in perf stat report]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if ! perf script -i "${stat_data}" 2>&1 | grep -E -q "metric:.*insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed missing metric in perf script]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if ! perf stat record -o "${stat_data}" -e instructions,cycles true > /dev/null 2>&1
+  then
+    echo "stat record metrics test [Failed record with events]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if perf stat report -i "${stat_data}" 2>&1 | grep -q "insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed unexpected metric in perf stat report]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if ! perf stat report --all-metrics -i "${stat_data}" 2>&1 | grep -q "insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed missing metric in perf stat report --all-metrics]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if perf script -i "${stat_data}" 2>&1 | grep -E -q "metric:.*insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed unexpected metric in perf script]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if ! perf script --all-metrics -i "${stat_data}" 2>&1 | grep -E -q "metric:.*insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed missing metric in perf script --all-metrics]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  stat_config=$(mktemp /tmp/perf-stat-test-config.XXXXX)
+  printf "[stat]\nall-metrics = true\n" > "${stat_config}"
+  if ! PERF_CONFIG="${stat_config}" perf stat report -i "${stat_data}" 2>&1 | \
+    grep -q "insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed stat report stat.all-metrics=true]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if ! PERF_CONFIG="${stat_config}" perf script -i "${stat_data}" 2>&1 | \
+    grep -E -q "metric:.*insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed perf script stat.all-metrics=true]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  rm -f "${stat_config}"
+  stat_config=""
+  if ! perf stat report -M insn_per_cycle -i "${stat_data}" 2>&1 | grep -q "insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed missing metric in perf stat report -M insn_per_cycle]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if ! perf stat record -o "${stat_data}" true > /dev/null 2>&1
+  then
+    echo "stat record metrics test [Failed default record]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if ! perf stat report -i "${stat_data}" 2>&1 | grep -q "insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed missing default metric in perf stat report]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  if ! perf script -i "${stat_data}" 2>&1 | grep -E -q "metric:.*insn_per_cycle"
+  then
+    echo "stat record metrics test [Failed missing default metric in perf script]"
+    stat_record_metrics_cleanup
+    err=1
+    return
+  fi
+  stat_record_metrics_cleanup
+  echo "stat record metrics test [Success]"
+}
+
 test_stat_repeat_weak_groups() {
   echo "stat repeat weak groups test"
   if ! perf stat -e '{cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles}' \
@@ -601,6 +733,7 @@ test_null_stat
 test_offline_cpu_stat
 test_stat_record_report
 test_stat_record_script
+test_stat_record_metrics
 test_stat_repeat_weak_groups
 test_topdown_groups
 test_topdown_weak_groups
-- 
2.56.1.703.gf848ed807c-goog


^ permalink raw reply related	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 04/30] perf metricgroup: Don't prefer tool events for displaying a metric
  2026-11-13 19:53 ` [PATCH v2 04/30] perf metricgroup: Don't prefer tool events for displaying a metric Ian Rogers
@ 2026-11-13 19:59   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 19:59 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> pick_display_evsel() selects the event of a metric that perf stat shows
> the metric alongside, preferring an event that isn't a tool event, such
> as duration_time, and that is used by the fewest metrics. A tool event
> used by fewer metrics replaced a non-tool event, so whether the metric
> was displayed with a tool event depended on the order of the metric's
> events. For example, CPUs_utilized could be shown alongside the
> duration_time tool event rather than cpu-clock, and cpu-clock not shown.
> 
> Only compare the number of uses of events that are both, or both not,
> tool events.
> 
> Fixes: c8035a496141 ("perf metricgroup: Add care to picking the evsel for displaying a metric")
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=4


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 17/30] perf dwarf-regs: Add e_machine__same_arch()
  2026-11-13 19:53 ` [PATCH v2 17/30] perf dwarf-regs: Add e_machine__same_arch() Ian Rogers
@ 2026-11-13 20:00   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:00 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> The 32-bit and 64-bit variants of some architectures have different ELF
> machines, such as EM_386 and EM_X86_64, while others, such as RISC-V,
> MIPS and PA-RISC, use one ELF machine for both. Code wanting to know if
> two ELF machines are the same architecture either normalized the ELF
> machine itself, as in pmu_events_map__e_machine_matches(), or compared
> the strings from perf_env__arch(). perf_env__arch() maps EM_386 and
> EM_X86_64 to "x86", but distinguishes "riscv" and "riscv64", "mips" and
> "mips64", and "parisc" and "parisc64" by whether the kernel is 64-bit,
> and computing it requires string lookups.
> 
> Add e_machine__64bit(), giving the ELF machine of the 64-bit variant of
> an architecture, and e_machine__same_arch() next to EM_HOST. EM_ARM and
> EM_AARCH64 remain distinct architectures. Use them when matching
> pmu-events tables and in is_pmu_core_len(), which compares a perf_env's
> architecture with the host's.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=17


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 13/30] perf header: Pass e_machine to strcmp_cpuid_str()
  2026-11-13 19:53 ` [PATCH v2 13/30] perf header: Pass e_machine to strcmp_cpuid_str() Ian Rogers
@ 2026-11-13 20:01   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:01 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> The format of a CPUID depends on its architecture, but
> strcmp_cpuid_str() is only given the two CPUID strings and its
> architecture specific implementations assume the CPUIDs are for the
> architecture perf was built for.
> 
> Pass the ELF machine of the CPUIDs' architecture to strcmp_cpuid_str()
> so that CPUIDs can be compared according to their architecture rather
> than the host's. The pmu-events tables pass their e_machine, comparisons
> with the host's CPUID pass EM_HOST and the arm64 CPUID matching test
> passes EM_AARCH64. The architecture specific implementations don't yet
> use the argument.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=13


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 15/30] perf test: Make the CPUID matching test architecture independent
  2026-11-13 19:53 ` [PATCH v2 15/30] perf test: Make the CPUID matching test architecture independent Ian Rogers
@ 2026-11-13 20:02   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:02 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> The arm64 CPUID matching test called strcmp_cpuid_str(), whose arm64
> MIDR comparison was only built for arm64. Now that strcmp_cpuid_str()
> is given the ELF machine of the CPUIDs and all of its comparisons are
> in util/header.c, the test can run on every architecture.
> 
> Move the test from arch/arm64/tests to tests, as a "CPUID matching"
> suite with an "arm64 MIDR" test case.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=15


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 03/30] perf script: Don't drop metric samples for CPUs missing from the evsel
  2026-11-13 19:53 ` [PATCH v2 03/30] perf script: Don't drop metric samples for CPUs missing from the evsel Ian Rogers
@ 2026-11-13 20:02   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:02 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> perf script -F metric looks up the index of the sample's CPU in the
> evsel's CPU map and, when it isn't present, prints "Missing CPU map
> entry for CPU <n>" and ignores the sample. When the perf.data file
> doesn't record the CPUs of an event, as is the case for perf record,
> __perf_evlist__propagate_maps() sets the evsel's CPUs to the host's
> online CPUs. If a CPU is offline when perf script is run, or the
> perf.data file was recorded on a machine with more CPUs, then every
> sample on such a CPU is dropped from the metrics with a message, for
> example thousands of:
> 
>   Missing CPU map entry for CPU 1
> 
> perf script currently only supports global aggregation of the counts
> used for metrics, the counts of all CPUs are summed. So place the counts
> of a sample whose CPU isn't in the evsel's CPU map in the first CPU of
> the map, as was already done for samples without a CPU. This doesn't
> change the metric values computed for samples of the CPUs in the map,
> the summed counts just include the previously dropped samples, and so
> isn't a regression. Aggregating per CPU, and using the CPUs of the
> perf.data file's environment rather than the host's, would be future
> improvements.
> 
> Fixes: 3622990efaab ("perf script: Change metric format to use json metrics")
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=3


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 16/30] perf test: Add CPUID matching tests for other architectures
  2026-11-13 19:53 ` [PATCH v2 16/30] perf test: Add CPUID matching tests for other architectures Ian Rogers
@ 2026-11-13 20:03   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:03 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] perf test: two-hyphen RISC-V strings in
  test__cpuid_match_regex()
--

commit 119e605bf598b12e6b113f6de7ea78452df71ad9
Author: Ian Rogers <irogers@google.com>

perf test: Add CPUID matching tests for other architectures

This commit adds strcmp_cpuid_str() unit tests for exact matching, x86,
PowerPC, RISC-V, and s390 CPUID strings.

> diff --git a/tools/perf/tests/cpuid-match.c b/tools/perf/tests/cpuid-match.c
> index e3d4a349f393b..826380241a0f1 100644
> --- a/tools/perf/tests/cpuid-match.c
> +++ b/tools/perf/tests/cpuid-match.c
> @@ -36,8 +36,87 @@ static int test__cpuid_match_arm64(struct test_suite *test __maybe_unused,

[ ... ]

> +static int test__cpuid_match_regex(struct test_suite *test __maybe_unused,
> +				   int subtest __maybe_unused)
> +{
> +	/* Three hyphens in a RISC-V CPUID don't imply an x86 stepping. */
> +	TEST_ASSERT_VAL("riscv match",
> +			!strcmp_cpuid_str(EM_RISCV, "0x489-0x8000000000000007-0x[[:xdigit:]]+",
> +					  "0x489-0x8000000000000007-0x0000000000000001"));

[Severity: Low]
Does the RISC-V test here actually exercise the three-hyphen x86 stepping
case described in the comment?

is_full_x86_cpuid() checks for three '-' characters, whereas both RISC-V
mvendorid-marchid-mimpid strings above contain only two hyphens and would
still match even if routed through strcmp_x86_cpuid_str().  Using a
mapcpuid pattern with a character-range hyphen (for example,
"0x489-0x8000000000000[1-7]-0x[[:xdigit:]]+") would give mapcpuid three
hyphens while cpuid has two.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=16

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 01/30] perf pmus: Don't skip core PMUs as duplicates
  2026-11-13 19:53 ` [PATCH v2 01/30] perf pmus: Don't skip core PMUs as duplicates Ian Rogers
@ 2026-11-13 20:04   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:04 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> perf_pmus__scan_skip_duplicates() skips a PMU whose name, without a
> numeric suffix, matches the previous PMU's. This is intended for uncore
> PMUs, such as uncore_cha_0 and uncore_cha_1, that have the same events.
> The check was also applied to core PMUs, but core PMUs of heterogeneous
> systems may differ only by such a suffix while having different events.
> For example, ARM ACPI systems with two kinds of core name their PMUs
> armv8_pmuv3_0 and armv8_pmuv3_1, and devicetree systems may name them
> armv8_cortex_a53 and armv8_cortex_a57 where "a53" and "a57" are hex
> suffixes. Only the first core PMU was listed by "perf list" and the
> events of the other core PMUs were missing.
> 
> Before, on such a system, "perf list" and "perf list --unit=<pmu>" only
> showed the events of the first core PMU, the scan having skipped the
> others. After, the events of every core PMU are shown, as on Intel
> hybrid systems with cpu_core and cpu_atom PMUs. Uncore PMU deduplication
> is unchanged and pmu_deduped_name_len() already avoided removing the
> suffix of core PMU names when printing events. Similarly avoid removing
> the suffix of core PMU names in the raw event encodings that "perf list"
> shows for each PMU, so that each core PMU's encoding is shown with its
> own name.
> 
> Fixes: cd4e1efbbc40 ("perf pmus: Skip duplicate PMUs and don't print list suffix by default")
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=1


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 02/30] perf script: Fix metrics computed from samples
  2026-11-13 19:53 ` [PATCH v2 02/30] perf script: Fix metrics computed from samples Ian Rogers
@ 2026-11-13 20:05   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:05 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> perf script -F metric computes metrics from the sum of sample periods of
> the events, but the computed values were wrong and the output was
> garbled:
> 
>  - perf_stat_process_counter() replaces each count with its delta from
>    the previous raw count. As counts were left as deltas, the next sample
>    computed negative deltas for the counts of all CPUs and threads other
>    than the sample's. Restore the counts to the running totals after
>    processing.
> 
>  - Metrics for all events were displayed after every sample. Only display
>    the metrics associated with the sampled event.
> 
>  - The new_line callback started a metric line that the next metric
>    started again. Each metric starts its own line, so make it a no-op.
> [ ... ]
> of the sample periods of all CPUs.
> 
> Fixes: 4bd1bef8bba2 ("perf script: Allow computing 'perf stat' style metrics")
> Fixes: 3622990efaab ("perf script: Change metric format to use json metrics")
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=2


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 05/30] perf env: Make lazy perf_env initialization thread-safe
  2026-11-13 19:53 ` [PATCH v2 05/30] perf env: Make lazy perf_env initialization thread-safe Ian Rogers
@ 2026-11-13 20:05   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:05 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> Guard lazy initialization of kernel_is_64_bit, nr_cpus_avail, e_machine,
> cpuid, nr_pmu_mappings, pmu_mappings, and numa_map in struct perf_env
> with env->lock, and use DO_ONCE() to initialize the cached host utsname
> structure. This prevents data races when multiple threads concurrently
> query a shared struct perf_env.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=5


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 14/30] perf header: Support CPUID matching across all architectures
  2026-11-13 19:53 ` [PATCH v2 14/30] perf header: Support CPUID matching across all architectures Ian Rogers
@ 2026-11-13 20:05   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:05 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> strcmp_cpuid_str() had architecture specific overrides of a __weak
> regular expression matching implementation in arch/x86/util/header.c
> and arch/arm64/util/header.c, so a CPUID could only be compared using
> the rules of the architecture perf was built for. In addition, the
> HEADER_CPUID strings written by get_cpuid() on x86 ("vendor,family,
> model,step" in decimal) and PowerPC ("pvr_ver,pvr_rev" in decimal)
> differ from the get_cpuid_str() formats ("vendor-family-model-stepping"
> in hex and "0x%.8lx") matched against the pmu-events mapfile.csv
> tables. As a result, finding the core events or metrics table for the
> CPUID of a perf_env, such as from a perf.data file recorded on a
> different architecture, failed.
> 
> Move the architecture specific comparisons into util/header.c and
> select them using the ELF machine of the CPUIDs: EM_NONE for the test
> and common tables compares exactly, arm64 compares MIDR part, variant
> and revision fields, x86 matches with an optional stepping and other
> architectures match a regular expression. Convert x86 and PowerPC
> HEADER_CPUID strings before matching.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=14


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 12/30] perf pmu-events: Pass e_machine to find_core_*_table()
  2026-11-13 19:53 ` [PATCH v2 12/30] perf pmu-events: Pass e_machine to find_core_*_table() Ian Rogers
@ 2026-11-13 20:05   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:05 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> find_core_events_table() and find_core_metrics_table() found tables by
> an architecture name, such as from perf_env__arch(), that has to match
> the pmu-events architecture directory name, for example "riscv" rather
> than the "riscv64" from uname.
> 
> Instead find tables by the ELF machine of the CPUID's architecture,
> using the e_machine recorded in struct pmu_events_map. The test and
> common tables are found with EM_NONE.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=12


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 10/30] perf pmus: Pass struct perf_pmus to scan helpers
  2026-11-13 19:53 ` [PATCH v2 10/30] perf pmus: Pass struct perf_pmus to scan helpers Ian Rogers
@ 2026-11-13 20:06   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:06 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> Update perf_pmus__scan() and perf_pmus__scan_core() to take a
> struct perf_pmus * argument and update callers to pass the appropriate
> environment's PMUs.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=10


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 07/30] perf pmu: Add from_env parameter to perf_pmu__init()
  2026-11-13 19:53 ` [PATCH v2 07/30] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers
@ 2026-11-13 20:10   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:10 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> Add a boolean from_env field to struct perf_pmu initialized via
> perf_pmu__init() to distinguish PMUs reconstructed from perf_env
> headers from live host PMUs. Guard tp_pmu, hwmon_pmu, and drm_pmu
> callbacks on !pmu->from_env so perf.data-derived PMUs do not access
> host tracepoints or cast plain struct perf_pmu allocations via
> container_of(). The hwmon and drm evsel open and read callbacks fail
> with -EINVAL for such PMUs.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=7


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 09/30] perf pmus: Pass struct perf_pmus to find helpers
  2026-11-13 19:53 ` [PATCH v2 09/30] perf pmus: Pass struct perf_pmus to find helpers Ian Rogers
@ 2026-11-13 20:11   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:11 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> Update perf_pmus__find(), perf_pmus__find_by_type(), and
> perf_pmus__find_core_pmu() to take a struct perf_pmus * argument and
> update callers to pass the appropriate environment's PMUs.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=9


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 11/30] perf pmu-events: Add e_machine to struct pmu_events_map
  2026-11-13 19:53 ` [PATCH v2 11/30] perf pmu-events: Add e_machine to struct pmu_events_map Ian Rogers
@ 2026-11-13 20:12   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:12 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> The CPUID strings in the pmu-events mapfile.csv files have an
> architecture specific format, such as "GenuineIntel-6-55-[56789ABCDEF]"
> on x86 or a MIDR value like "0x00000000410fd0c0" on arm64. When perf is
> built with JEVENTS_ARCH=all the tables of every architecture are
> present and map_for_cpu() compared the host's CPUID against all of
> them, even though only the host architecture's tables can match.
> 
> Record the ELF machine of each architecture's CPUIDs in struct
> pmu_events_map, EM_NONE for the test and common tables, and have
> map_for_cpu() only consider tables for the host's ELF machine. The 32
> and 64-bit variants of x86 and PowerPC share tables.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=11


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 22/30] perf pmu: Add perf_pmu__for_each_name_from_config()
  2026-11-13 19:53 ` [PATCH v2 22/30] perf pmu: Add perf_pmu__for_each_name_from_config() Ian Rogers
@ 2026-11-13 20:13   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:13 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> perf_pmu__name_from_config() returns the first event alias of a PMU
> whose parsed config matches, ignoring the perf_event_attr type. So a
> legacy hardware event alias can be returned for a raw config of the
> same value, only one of possibly several names is found, and the names
> of legacy events whose aliases were replaced by sysfs or json events of
> the same name can't be found.
> 
> Add perf_pmu__for_each_name_from_config() that calls a callback for
> each event name of a PMU whose encoding has a given type and config,
> ignoring the extended PMU type of legacy encodings and, for core PMUs,
> also searching the legacy hardware and cache event tables. Make
> perf_pmu__name_from_config() return the first such name for the PMU's
> type.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=22


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 30/30] perf test: Add stat record metrics test
  2026-11-13 19:53 ` [PATCH v2 30/30] perf test: Add stat record metrics test Ian Rogers
@ 2026-11-13 20:14   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:14 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> Test that metrics measured by perf stat record are displayed by perf
> stat report and perf script, and that events recorded with -e only
> display matching JSON metrics in perf stat report and perf script when
> --all-metrics, stat.all-metrics=true, or -M is used.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=30


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 24/30] perf stat: Share events between metrics and command line events
  2026-11-13 19:53 ` [PATCH v2 24/30] perf stat: Share events between metrics and command line events Ian Rogers
@ 2026-11-13 20:14   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:14 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> The metrics added by -T, --smi-cost, --topdown and the Default metric
> groups were parsed into a separate evlist and then added to the events
> from the command line, so they didn't share events with them or, for
> the Default metric groups, with each other.
> 
> Parse them directly into evsel_list so that the evsel matcher of
> metricgroup__parse_groups() shares events counting the same thing. Only
> mark the newly added events as skippable and keep the evsel indices in
> list order after sorting the Default metric events, as required when
> writing the group descriptions of a perf.data file. This removes the
> copying of the metric events from the separate evlist with
> metricgroup__copy_metric_events(), which crashed for "perf stat -T" on
> an Alder Lake system.
> 
> For example, "perf stat -e duration_time -T" now uses the duration_time
> event from the command line for the transaction metrics.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=24


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 21/30] perf evsel: Generalize metric_id to a set of metric_ids
  2026-11-13 19:53 ` [PATCH v2 21/30] perf evsel: Generalize metric_id to a set of metric_ids Ian Rogers
@ 2026-11-13 20:31   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:31 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> When computing a metric the value of each of its events is added to the
> expression context using the event's metric_id. When metrics are matched
> to events read from a perf.data file, different metrics may refer to the
> same recorded event by different names, for example "instructions" and
> "cpu_core@INST_RETIRED.ANY@", but an evsel previously stored only a
> single metric_id.
> 
> Replace evsel::metric_id with a NULL-terminated evsel::metric_ids set of
> unique identifier strings. Keep the representation private to evsel.c
> behind helpers: evsel__add_metric_id(), evsel__copy_metric_ids(),
> evsel__has_metric_id(), evsel__shares_metric_id() and
> evsel__for_each_metric_id(), a callback based iterator. Use the iterator
> in prepare_metric() to populate the expression parse context with all
> metric_ids of each metric event. The metric_ids are freed by
> evsel__exit(), so make the __add_event() error path use evsel__put()
> rather than freeing the evsel's strings itself.
> 
> Remove evsel__metric_id() as returning a single identifier from the set
> is error prone. Its uses either iterate over the set, test membership,
> or, when finding duplicate events for a metric leader, test whether two
> evsels share an identifier. Tool events without an explicit metric-id
> keep their tool event name as an implicit identifier, other events
> without a metric-id no longer have the identifier "unknown".
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=21


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 20/30] perf parse-events: Decode metric-id terms when adding events
  2026-11-13 19:53 ` [PATCH v2 20/30] perf parse-events: Decode metric-id terms when adding events Ian Rogers
@ 2026-11-13 20:33   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:33 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> Metric events are parsed from an event string where the identifier from
> the metric expression is passed as a metric-id term. As characters such
> as '@' and ',' can't appear in a term value the metricgroup code encodes
> them and, after parsing, walked the parsed evlist replacing each evsel's
> metric_id with its decoded version.
> 
> Move the encoding and decoding to parse-events.c, adding
> parse_events__encode_metric_id() for the metricgroup code, and store the
> decoded metric-id on the evsel when it is created in __add_event(). This
> avoids rewriting the metric_id of evsels after parsing, leaving
> metricgroup with only the replacement of the evsel names of metric
> events by friendlier versions of the metric-id. While moving the
> decoding, fix the out of bounds check on the encoded character index
> and return -EINVAL for a bad encoding.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=20


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 08/30] perf pmus: Introduce struct perf_pmus and perf_env__pmus()
  2026-11-13 19:53 ` [PATCH v2 08/30] perf pmus: Introduce struct perf_pmus and perf_env__pmus() Ian Rogers
@ 2026-11-13 20:36   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:36 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> Move the static global core_pmus, other_pmus, read_pmu_types,
> num_core_pmus, and extended_type state from pmus.c into a new
> struct perf_pmus embedded in struct perf_env and accessed via
> perf_env__pmus().
> 
> Rename perf_pmus__destroy() to perf_pmus__exit() to match the
> perf_env__init()/perf_env__exit() naming convention, and update it
> along with perf_pmus__find_by_attr(), perf_pmus__scan_for_event(),
> perf_pmus__scan_matching_wildcard(), perf_pmus__scan_for_uncore_id(),
> perf_pmus__pmu_for_pmu_filter(), perf_pmus__print_pmu_events(),
> perf_pmus__print_raw_pmu_events(), perf_pmus__have_event(),
> perf_pmus__num_core_pmus(), perf_pmus__supports_extended_type(),
> perf_pmus__add_test_pmu(), and perf_pmus__add_test_hwmon_pmu() to take
> struct perf_pmus *.
> 
> To keep this commit reviewable, the high-fanout perf_pmus__find(),
> perf_pmus__find_by_type(), perf_pmus__find_core_pmu(),
> perf_pmus__scan(), and perf_pmus__scan_core() helpers are updated to
> take struct perf_pmus * in the next two commits.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=8


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 18/30] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric()
  2026-11-13 19:53 ` [PATCH v2 18/30] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric() Ian Rogers
@ 2026-11-13 20:37   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:37 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> metricgroup__sys_event_iter() matched system metrics against uncore
> PMUs using the live host's PMUs, so when processing a perf.data file
> from another machine system metrics were matched against the wrong PMU
> topology. Add a struct perf_pmus * argument to
> metricgroup__for_each_metric() and use it to match the metric's compat
> identifier. Callers pass the PMUs of the environment they are working
> on.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=18


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 26/30] perf metricgroup: Improve matching of metrics to recorded events
  2026-11-13 19:53 ` [PATCH v2 26/30] perf metricgroup: Improve matching of metrics to recorded events Ian Rogers
@ 2026-11-13 20:37   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:37 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> Matching metrics to the events of a perf.data file compared only the
> perf_event_attr type and config of events, which failed for common
> cases:
> 
>  - Legacy hardware events, such as "cycles" with "perf record -e
>    cycles", didn't match the sysfs event, such as "cpu_core/cpu-cycles/",
>    that metrics are parsed to. Use the evsel matcher, as used for metrics
>    sharing events, which compares the PMU name and counter encoding,
>    converting legacy hardware events of core PMUs to the equivalent
>    sysfs event's encoding. Add an EVSEL_MATCH_RECORDED mode to the
>    matcher that tolerates the exclusion bits evsel__fallback() may have
>    changed when recording, skips unsupported events and prefers events
>    with counts.
> 
>  - A recorded event could be used by only one metric leader, as metrics
> [ ... ]
> where the json metrics use sysfs and json encodings of the events, such
> as cpu_core/INST_RETIRED.ANY/, that now match the recorded legacy
> events.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=26


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 27/30] perf metricgroup: Add metricgroup__add_recorded_metrics()
  2026-11-13 19:53 ` [PATCH v2 27/30] perf metricgroup: Add metricgroup__add_recorded_metrics() Ian Rogers
@ 2026-11-13 20:42   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:42 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> Add metricgroup__add_matching_metric_groups() and
> metricgroup__add_recorded_metrics() to parse metrics or metricgroups
> requested on a recorded perf stat command line (or the Default
> metricgroups when no events or metrics were explicitly specified) and
> match them against the recorded events in the evlist.
> 
> When reporting commands such as 'perf stat report' or 'perf script'
> inspect a perf.data file recorded by 'perf stat record', this allows
> reconstructing the exact metrics requested at record time (including -M,
> --transaction, --smi-cost, --topdown, -d, --cputype, and --pmu-filter)
> or optionally adding all matching JSON metrics when --all-metrics is
> enabled.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=27


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 28/30] perf stat report: Display metrics for recorded events
  2026-11-13 19:53 ` [PATCH v2 28/30] perf stat report: Display metrics for recorded events Ian Rogers
@ 2026-11-13 20:44   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:44 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> perf stat report displays the recorded counts but not the metrics that
> perf stat record displayed, as metrics aren't recorded in the perf.data
> file. Inspect the recorded command line in struct perf_env and display
> the metrics requested on the perf stat record command line (or the
> default metrics when no events or metrics were explicitly specified),
> for example:
> 
>   $ perf stat record -M insn_per_cycle -a sleep 1
>   $ perf stat report
>   ...
>     784,599,279  cpu_atom/cpu-cycles/   # 0.7 instructions  insn_per_cycle
>     586,070,692  cpu_atom/instructions/
>     776,737,361  cpu_core/cpu-cycles/   # 1.3 instructions  insn_per_cycle
>   1,011,699,468  cpu_core/instructions/
> 
>     1.004467030 seconds time elapsed
> 
> Also add --all-metrics (and the stat.all-metrics config option) to
> display all metrics whose events were recorded in the perf.data file,
> as well as -M/--metrics and --metric-only options to perf stat report.
> Set stat_config.system_wide from the recorded thread map as metrics
> such as CPUs_utilized depend upon it.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=28


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 06/30] perf env: Add host_env() singleton
  2026-11-13 19:53 ` [PATCH v2 06/30] perf env: Add host_env() singleton Ian Rogers
@ 2026-11-13 20:47   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:47 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] perf header: write_cmdline() conditional host_env() fallback
  corrupts perf inject HEADER_CMDLINE
- [Low] perf trace: direct cmd_trace() call in main() skips host_env()
  setup/exit
--

commit 514f7c0886cdcd7ffafc72b5f2dce5946ffe0612
Author: Ian Rogers <irogers@google.com>

perf env: Add host_env() singleton

This commit introduces a statically initialized host_env() singleton to
represent the live host environment and drops the struct perf_env *
parameter from machine__new_host(), machine__new_kallsyms(),
machine__new_live(), and __perf_session__new().

> diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
> index 4094141461f91..fb02fa6dd4377 100644
> --- a/tools/perf/builtin-trace.c
> +++ b/tools/perf/builtin-trace.c
[ ... ]
> @@ -2121,20 +2120,14 @@ static char *trace__machine__resolve_kernel_addr(void *vmachine, unsigned long l
>  	return machine__resolve_kernel_addr(vmachine, addrp, modp);
>  }
>  
> -static int trace__symbols_init(struct trace *trace, int argc, const char **argv,
> -			       struct evlist *evlist)
> +static int trace__symbols_init(struct trace *trace, struct evlist *evlist)
>  {
>  	int err = symbol__init(NULL);
>  
>  	if (err)
>  		return err;
>  
> -	perf_env__init(&trace->host_env);
> -	err = perf_env__set_cmdline(&trace->host_env, argc, argv);
> -	if (err)
> -		goto out;
> -
> -	trace->host = machine__new_host(&trace->host_env);
> +	trace->host = machine__new_host();
>  	if (trace->host == NULL) {
>  		err = -ENOMEM;
>  		goto out;
> @@ -2155,10 +2148,8 @@ static int trace__symbols_init(struct trace *trace, int argc, const char **argv,
>  					    /*mmap_data=*/false,
>  					    /*nr_threads_synthesize=*/1);
>  out:
> -	if (err) {
> -		perf_env__exit(&trace->host_env);
> +	if (err)
>  		symbol__exit();
> -	}
>  	return err;
>  }
>  
> @@ -2167,7 +2158,6 @@ static void trace__symbols__exit(struct trace *trace)
>  	machine__delete(trace->host);
>  	trace->host = NULL;
>  
> -	perf_env__exit(&trace->host_env);
>  	symbol__exit();
>  }
[ ... ]
> diff --git a/tools/perf/perf.c b/tools/perf/perf.c
> index d60bc929c2572..bcbc12295a387 100644
> --- a/tools/perf/perf.c
> +++ b/tools/perf/perf.c
> @@ -380,12 +381,14 @@ static int run_builtin(const struct cmd_struct *p, int argc, const char **argv)
>  		use_pager = 1;
>  	commit_pager_choice();
>  
> +	perf_env__set_cmdline(host_env(), argc, argv);
>  	status = p->fn(argc, argv);
>  	perf_config__exit();
>  #ifdef HAVE_LIBTRACEEVENT
>  	trace_event__exit();
>  #endif
>  	exit_browser(status);
> +	perf_env__exit(host_env());
>  
>  	if (status)
>  		return status & 0xff;

[Severity: Low]
Does moving perf_env__set_cmdline() and perf_env__exit() from
trace__symbols_init() and trace__symbols__exit() into run_builtin() miss
the direct cmd_trace() invocation in main()?

tools/perf/perf.c:main() {
	...
	if (strstarts(cmd, "trace")) {
		setup_path();
		argv[0] = "trace";
		return cmd_trace(argc, argv);
	}
	...
}

When invoked via the trace alias, main() calls cmd_trace() directly
without going through run_builtin(), leaving host_env()->cmdline_argv
unpopulated for trace record and skipping perf_env__exit(host_env()) on
exit.

[ ... ]

> diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
> index 9eec6deee190f..b6f86f557cafe 100644
> --- a/tools/perf/util/header.c
> +++ b/tools/perf/util/header.c
> @@ -670,6 +670,9 @@ static int write_cmdline(struct feat_fd *ff,
>  	char pbuf[MAXPATHLEN], *buf;
>  	int i, ret, n;
>  
> +	if (!env->cmdline_argv)
> +		env = host_env();
> +
>  	/* actual path to perf binary */
>  	buf = perf_exe(pbuf, MAXPATHLEN);
>  

[Severity: Medium]
Can checking !env->cmdline_argv here corrupt HEADER_CMDLINE when perf
inject regenerates the output header?

When perf inject reads the input perf.data header, process_cmdline()
populates ff->ph->env.cmdline_argv from the input file, with the perf
binary path already stored at cmdline_argv[0].  Because keep_feat()
returns false for HEADER_CMDLINE, perf_session__inject_header() calls
write_cmdline() to update the feature section.  With env->cmdline_argv
already non-NULL, write_cmdline() will not switch to host_env(), so it
reuses the input file's command line instead of recording the perf inject
invocation and prepends perf_exe() a second time.  As noted above,
invoking trace directly via main() -> cmd_trace() also bypasses
perf_env__set_cmdline(host_env(), ...), leaving host_env()->cmdline_argv
NULL when write_cmdline() falls back to host_env().

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=6

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 29/30] perf script: Display metrics for perf stat record data
  2026-11-13 19:53 ` [PATCH v2 29/30] perf script: Display metrics for perf stat record data Ian Rogers
@ 2026-11-13 20:47   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:47 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> perf script displays the counts of each stat round read from a perf stat
> record perf.data file. Also display, after each round, the metrics
> requested on the perf stat record command line (or all matching metrics
> when --all-metrics or stat.all-metrics=true is used), computed from the
> counts aggregated across CPUs and threads, for example:
> 
>   $ perf stat record -M insn_per_cycle -a sleep 1
>   $ perf script
>   CPU THREAD         VAL         ENA         RUN        TIME EVENT
>   ...
>   cpu_atom/cpu-cycles/  metric:      0.7 instructions  insn_per_cycle
>   cpu_core/cpu-cycles/  metric:      1.3 instructions  insn_per_cycle
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=29


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 23/30] perf metricgroup: Share metric events by their counter encoding
  2026-11-13 19:53 ` [PATCH v2 23/30] perf metricgroup: Share metric events by their counter encoding Ian Rogers
@ 2026-11-13 20:59   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:59 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] perf metricgroup: counter_key__equal() ignores evsel CPU mask
--

commit 0da7bf02bed3bcc6d69ab37b6322595a7c6b2573
Author: Ian Rogers <irogers@google.com>

perf metricgroup: Share metric events by their counter encoding

This commit replaces identifier-name metric event sharing with an evsel
matcher that compares counter encodings, modifiers, and cgroups so metrics
can share equivalent events across expressions and with events already in
the evlist.

> diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
> index f25709750c3e1..f4a2b280091cd 100644
> --- a/tools/perf/util/metricgroup.c
> +++ b/tools/perf/util/metricgroup.c

[ ... ]

> @@ -471,6 +479,512 @@ int metricgroup__for_each_metric(struct perf_pmus *pmus, const struct pmu_metric
>  	return pmu_for_each_sys_metric(metricgroup__sys_event_iter, &sys_data);
>  }
>  
> +/*
> + * A key identifying the counter an evsel programs, used to find evsels counting
> + * the same thing, such as when metrics share events.
> + */
> +struct counter_key {
> +	/** @pmu_name: Name of the evsel's PMU, or NULL if unknown. */
> +	const char *pmu_name;
> +	/** @cgrp: The cgroup the evsel counts. */
> +	const struct cgroup *cgrp;

[ ... ]

> +static void evsel_matcher__key(struct evsel_matcher *matcher, struct evsel *evsel,
> +			       struct counter_key *key)
> +{
> +	struct perf_pmu *pmu = evsel__find_pmu(evsel);
> +
> +	counter_key__init_attr(key, pmu ? pmu->name : NULL, &evsel->core.attr);
> +	key->cgrp = evsel->cgrp;
> +	if (evsel->percore)
> +		key->modifiers |= COUNTER_KEY_MOD_PERCORE;
> +	if (evsel__is_retire_lat(evsel))
> +		key->modifiers |= COUNTER_KEY_MOD_RETIRE_LAT;
> +	if (evsel->precise_max)
> +		key->modifiers |= COUNTER_KEY_MOD_PRECISE_MAX;
> +	if (evsel->bpf_counter)
> +		key->modifiers |= COUNTER_KEY_MOD_BPF;
> +	if (key->type == PERF_TYPE_HARDWARE && pmu && pmu->is_core)
> +		legacy_hw_key(matcher, pmu->name, key->config, key);
> +}
> +
> +/* Do @a and @b count the same thing? */
> +static bool counter_key__equal(const struct counter_key *a, const struct counter_key *b)
> +{
> +	if (a->type != b->type || a->config != b->config ||
> +	    a->config1 != b->config1 || a->config2 != b->config2 ||
> +	    a->config3 != b->config3 || a->config4 != b->config4)
> +		return false;
> +
> +	if ((a->type == PERF_TYPE_RAW || a->type >= PERF_TYPE_MAX) &&
> +	    !a->pmu_name != !b->pmu_name)
> +		return false;
> +
> +	if (a->pmu_name && b->pmu_name && strcmp(a->pmu_name, b->pmu_name))
> +		return false;
> +
> +	return a->exclude == b->exclude && a->modifiers == b->modifiers &&
> +	       a->precise_ip == b->precise_ip && a->cgrp == b->cgrp;
> +}

[Severity: Medium]
Should counter_key also compare evsel->core.cpus (for example with
perf_cpu_map__equal())?

On Intel hybrid platforms, JSON metrics specify per-PMU CPU restrictions
on shared PMUs, such as msr/tsc,cpu=cpu_core/, msr/tsc,cpu=cpu_atom/, and
unrestricted msr/tsc/.  Because parse_events_add_pmu() stores the cpu=
term in evsel->core.cpus without changing perf_event_attr or pmu_name,
counter_key__equal() treats these events as identical and
metric__adopt_events() merges them into whichever evsel was added first,
causing the second metric to read counts from the wrong CPU subset.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=23

^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 19/30] perf metricgroup: Move matching of metrics to recorded events from perf script
  2026-11-13 19:53 ` [PATCH v2 19/30] perf metricgroup: Move matching of metrics to recorded events from perf script Ian Rogers
@ 2026-11-13 20:59   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 20:59 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

> perf script finds metrics whose events are all present in the
> perf.data file's evlist so that they can be displayed alongside
> samples. Move this code into metricgroup.c as
> metricgroup__add_matching_metrics() so that it can be shared with
> other commands, such as perf stat report. No functional change.
> 
> Signed-off-by: Ian Rogers <irogers@google.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=19


^ permalink raw reply	[flat|nested] 61+ messages in thread

* Re: [PATCH v2 25/30] perf pmus: Add perf_pmus__read_from_env()
  2026-11-13 19:53 ` [PATCH v2 25/30] perf pmus: Add perf_pmus__read_from_env() Ian Rogers
@ 2026-11-13 21:05   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-11-13 21:05 UTC (permalink / raw)
  To: Ian Rogers; +Cc: linux-perf-users

Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] perf pmus: empty core_pmus in perf_pmus__read_from_env()
  without cpu caps
- [Medium] perf pmus: broken event config name lookup in
  env_pmu_find_or_add()
--

commit 62379d346fd8a3e273dd7df18d358ca721a7bc84
Author: Ian Rogers <irogers@google.com>

perf pmus: Add perf_pmus__read_from_env()

This commit adds perf_pmus__read_from_env() and perf_pmu__add_cap() to
reconstruct core and other PMU lists and capabilities from perf.data
header metadata, and lazily populates env->pmus in perf_env__pmus().

> diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
> index 6d68371107aa1..b924ff6180e41 100644
> --- a/tools/perf/util/pmus.c
> +++ b/tools/perf/util/pmus.c

[ ... ]

> @@ -981,3 +984,234 @@ struct perf_pmu *perf_pmus__fake_pmu(void)
>  
>  	return &fake;
>  }

[ ... ]

> +	pmu->pmus = pmus;
> +	pmu->sysfs_aliases_loaded = true;
> +	pmu->caps_initialized = true;
> +	pmu->max_precise = -1;
> +	pmu->is_core = env_is_pmu_core(name, env);
> +	pmu->auxtrace = !strcmp(name, "intel_pt") ||
> +			!strcmp(name, "intel_bts") ||
> +			!strcmp(name, "cs_etm") ||
> +			strstarts(name, "arm_spe") ||
> +			strstarts(name, "hisi_ptt");
> +	pmu->is_uncore = !pmu->is_core && !pmu->auxtrace &&
> +			 !perf_pmu__is_software(pmu) &&
> +			 (perf_pmu__kind(pmu) == PERF_PMU_KIND_PE ||
> +			  pmu->type == PERF_PMU_TYPE_FAKE);
> +
> +	if (!strcmp(name, "software") || !strcmp(name, "tool"))
> +		pmu->events_table = perf_pmu__default_core_events_table();
> +	else if (core_table && (pmu->is_core || pmu->is_uncore))
> +		pmu->events_table = core_table;

[Severity: Medium]
Does setting pmu->events_table on an env-backed PMU while leaving
pmu->format empty break perf_pmu__name_from_config() and leak help_msg?

When print_event_desc() or evsel__fprintf() calls
perf_event_attr__fprintf() with perf_env__pmus(env),
perf_pmu__for_each_name_from_config() loads the JSON aliases from
pmu->events_table via pmu_add_cpu_aliases() and calls
perf_pmu__parse_terms_to_attr() -> pmu_config_term() with err == NULL on
each alias.  Because pmu->format is empty, pmu_find_format() returns NULL
for every format term (such as event= or umask=):

tools/perf/util/pmu.c:pmu_config_term() {
    ...
	format = pmu_find_format(&pmu->format, term->config);
	if (!format) {
		char *pmu_term = pmu_formats_string(&pmu->format);
		char *unknown_term;
		char *help_msg;
    ...
		help_msg = parse_events_formats_error_string(pmu_term);
		if (err) {
			parse_events_error__handle(err, term->err_term,
						   unknown_term,
						   help_msg);
		} else {
			pr_debug("%s (%s)\n", unknown_term, help_msg);
			free(unknown_term);
		}
		free(pmu_term);
		return -EINVAL;
	}
    ...
}

As a result, perf_pmu__name_from_config() fails to resolve format-based
event names, and every alias iteration leaks help_msg in the !err branch.

[ ... ]

> +int perf_pmus__read_from_env(struct perf_pmus *pmus, struct perf_env *env)
> +{
> +	const struct pmu_events_table *core_table = NULL;
> +	struct perf_pmu *pmu;
> +
> +	if (!pmus || !env)
> +		return -EINVAL;
> +
> +	perf_pmus__exit(pmus);
> +	pmus->read_pmu_types = PERF_TOOL_PMU_TYPE_ALL_MASK;

[ ... ]

> +	if (env->nr_cpu_pmu_caps > 0 && env->cpu_pmu_caps) {
> +		pmu = pmu_find(pmus, "cpu");
> +		if (!pmu && !list_empty(&pmus->core_pmus)) {
> +			/* Match the writer's perf_pmus__find_core_pmu() order. */
> +			list_sort(NULL, &pmus->core_pmus, pmus_cmp);
> +			pmu = list_first_entry(&pmus->core_pmus, struct perf_pmu, list);
> +			if (pmu->nr_caps > 0)
> +				pmu = NULL;
> +		} else if (!pmu) {
> +			pmu = env_pmu_find_or_add(pmus, "cpu", PERF_TYPE_RAW,
> +						  env, core_table);
> +		}
> +		if (pmu)
> +			env_pmu_add_caps_strings(pmu, env->nr_cpu_pmu_caps, env->cpu_pmu_caps);
> +	}

[Severity: Medium]
Can perf_pmus__read_from_env() leave pmus->core_pmus empty when
env->nr_cpu_pmu_caps is 0?

Unlike pmu_read_sysfs(), which always creates a placeholder core PMU when
pmus->core_pmus is empty, the fallback env_pmu_find_or_add(pmus, "cpu",
PERF_TYPE_RAW, ...) here only runs when env->nr_cpu_pmu_caps > 0.  If a
perf.data file has HEADER_PMU_MAPPINGS or HEADER_PMU_CAPS without CPU PMU
capabilities and no PMU name matches env_is_pmu_core() (for example, a VM
recording with only software and tracepoint PMUs), pmus->core_pmus stays
empty while pmus->read_pmu_types is set to PERF_TOOL_PMU_TYPE_ALL_MASK,
preventing pmu_read_sysfs() from creating the placeholder.
perf_pmus__find_core_pmu() and evsel__find_pmu() then return NULL for
PERF_TYPE_HARDWARE, PERF_TYPE_HW_CACHE, and PERF_TYPE_RAW events.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261113195335.2865725-1-irogers@google.com?part=25

^ permalink raw reply	[flat|nested] 61+ messages in thread

end of thread, other threads:[~2026-11-13 21:05 UTC | newest]

Thread overview: 61+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 19:53 [PATCH v2 00/30] perf: Per-env PMUs, metric event sharing, and recorded event metrics Ian Rogers
2026-11-13 19:53 ` [PATCH v2 01/30] perf pmus: Don't skip core PMUs as duplicates Ian Rogers
2026-11-13 20:04   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 02/30] perf script: Fix metrics computed from samples Ian Rogers
2026-11-13 20:05   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 03/30] perf script: Don't drop metric samples for CPUs missing from the evsel Ian Rogers
2026-11-13 20:02   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 04/30] perf metricgroup: Don't prefer tool events for displaying a metric Ian Rogers
2026-11-13 19:59   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 05/30] perf env: Make lazy perf_env initialization thread-safe Ian Rogers
2026-11-13 20:05   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 06/30] perf env: Add host_env() singleton Ian Rogers
2026-11-13 20:47   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 07/30] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers
2026-11-13 20:10   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 08/30] perf pmus: Introduce struct perf_pmus and perf_env__pmus() Ian Rogers
2026-11-13 20:36   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 09/30] perf pmus: Pass struct perf_pmus to find helpers Ian Rogers
2026-11-13 20:11   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 10/30] perf pmus: Pass struct perf_pmus to scan helpers Ian Rogers
2026-11-13 20:06   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 11/30] perf pmu-events: Add e_machine to struct pmu_events_map Ian Rogers
2026-11-13 20:12   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 12/30] perf pmu-events: Pass e_machine to find_core_*_table() Ian Rogers
2026-11-13 20:05   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 13/30] perf header: Pass e_machine to strcmp_cpuid_str() Ian Rogers
2026-11-13 20:01   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 14/30] perf header: Support CPUID matching across all architectures Ian Rogers
2026-11-13 20:05   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 15/30] perf test: Make the CPUID matching test architecture independent Ian Rogers
2026-11-13 20:02   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 16/30] perf test: Add CPUID matching tests for other architectures Ian Rogers
2026-11-13 20:03   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 17/30] perf dwarf-regs: Add e_machine__same_arch() Ian Rogers
2026-11-13 20:00   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 18/30] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric() Ian Rogers
2026-11-13 20:37   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 19/30] perf metricgroup: Move matching of metrics to recorded events from perf script Ian Rogers
2026-11-13 20:59   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 20/30] perf parse-events: Decode metric-id terms when adding events Ian Rogers
2026-11-13 20:33   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 21/30] perf evsel: Generalize metric_id to a set of metric_ids Ian Rogers
2026-11-13 20:31   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 22/30] perf pmu: Add perf_pmu__for_each_name_from_config() Ian Rogers
2026-11-13 20:13   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 23/30] perf metricgroup: Share metric events by their counter encoding Ian Rogers
2026-11-13 20:59   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 24/30] perf stat: Share events between metrics and command line events Ian Rogers
2026-11-13 20:14   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 25/30] perf pmus: Add perf_pmus__read_from_env() Ian Rogers
2026-11-13 21:05   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 26/30] perf metricgroup: Improve matching of metrics to recorded events Ian Rogers
2026-11-13 20:37   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 27/30] perf metricgroup: Add metricgroup__add_recorded_metrics() Ian Rogers
2026-11-13 20:42   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 28/30] perf stat report: Display metrics for recorded events Ian Rogers
2026-11-13 20:44   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 29/30] perf script: Display metrics for perf stat record data Ian Rogers
2026-11-13 20:47   ` sashiko-bot
2026-11-13 19:53 ` [PATCH v2 30/30] perf test: Add stat record metrics test Ian Rogers
2026-11-13 20:14   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).