mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo
@ 2026-11-03 23:44 Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 01/41] perf env: Make lazy perf_env initialization thread-safe Ian Rogers
                   ` (40 more replies)
  0 siblings, 41 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

This series refactors PMU state management into struct perf_env, enables
computing and displaying metrics from recorded perf.data files in
'perf stat report' and 'perf script', adds live PMU capability probing,
and introduces the 'perf sysinfo' subcommand.

It follows on from the v2 RFC series ("perf sysinfo: Add subcommand to
probe and summarize PMU capabilities"):
https://lore.kernel.org/linux-perf-users/20261017185442.2137623-1-irogers@google.com/
Because the series has grown substantially beyond 'perf sysinfo' with
the per-perf_env PMU refactoring and 'perf stat record'/'perf stat report'
metric support, it is being sent as a non-RFC v1 series.

Overview of the series
======================

1. Per-perf_env PMU state (struct perf_pmus) and perf.data reconstruction
   (Patches 1-13):
   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 1 makes lazy perf_env initialization thread-safe using
     env->env_sem.
   - Patch 2 introduces the host_env() singleton to represent the live
     host environment explicitly, replacing the global perf_env variable
     and session->header.env fallbacks.
   - Patch 3 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 4-6 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 7-9 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 10 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 11-12 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 13 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.

2. Recorded event metrics in 'perf stat report' and 'perf script'
   (Patches 14-25):
   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 14 passes struct perf_pmus to metricgroup__for_each_metric()
     and pmu_metrics_table__find().
   - Patch 15 moves the matching of metrics to recorded events
     (metricgroup__add_matching_metrics()) from builtin-script.c into
     util/metricgroup.c.
   - Patch 16 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 17 generalizes evsel::metric_id to a set of unique
     evsel::metric_ids. When events are parsed from metric groups, the
     metric-id is part of the parsed event name and stored on the evsel.
     When matching metrics to recorded events in a perf.data file,
     different metrics may refer to the same recorded evsel using
     different metric-ids (or the recorded event may have been recorded
     without a metric-id); storing a set of metric_ids on the evsel
     allows prepare_metric() to populate the expression context with all
     identifiers associated with the evsel. 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. evsel__metric_id(), which would arbitrarily return the
     first identifier of the set, is removed.
   - Patch 18 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().
   - Patch 19 improves metricgroup__add_matching_metrics() to parse each
     candidate metric's events against the target struct perf_pmus and
     match them to recorded evlist entries by normalized hardware
     counter keys (pmu_type, config, config1, config2, config3, and
     privilege exclusion bits), supporting PERF_TYPE_RAW extended PMU
     type encodings, uncore PMU instance fan-out, and perf_event_paranoid
     privilege exclusion fallbacks.
   - Patch 20 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 21 fixes 'perf script -F +metric' sample-based metric
     calculation to use per-sample period deltas rather than raw
     monotonically increasing counter values.
   - Patch 22 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 23 enables displaying metrics for recorded events in
     'perf stat report' (and adds --all-metrics and stat.all-metrics).
   - Patch 24 enables displaying metrics for 'perf stat record'
     PERF_RECORD_STAT data in 'perf script' (and adds --all-metrics).
   - Patch 25 adds a shell test verifying metric reporting across
     'perf stat record', 'perf stat report', and 'perf script'.

3. Live PMU capability probing via perf_pmu__probe() (Patches 26-38):
   - Patch 26 exports perf_pmus__scan_skip_duplicates() and adds
     perf_pmu__num_duplicates() to count numbered uncore PMU instances.
   - Patch 27 exports perf_pmu__mem_events_init_pmu() and
     perf_pmu__mem_events_name() for per-PMU memory profiling events.
   - Patches 28-38 add struct perf_pmu::probed and perf_pmu__probe(),
     split into individual capability probes:
     - Patch 28: Basic event opening, counting, interrupt sampling, and
       branch stack sampling (PERF_SAMPLE_BRANCH_STACK).
     - Patch 29: Supported perf_event_sample_format flags (sample_type).
     - Patch 30: Supported precise_ip levels (:p, :pp, :ppp).
     - Patch 31: Privilege and execution-mode exclusion modifiers
       (exclude_user, exclude_kernel, exclude_hv, exclude_idle,
       exclude_host, exclude_guest).
     - Patch 32: Execution and AUX area features (leader_sampling,
       sw_grouping, auto-counter reload, aux_output, aux_pause,
       aux_sample, inherit, sample_freq, write_backward, callchain,
       data_src, weight, pinned, exclusive, format_lost, cgroup).
     - Patch 33: Event group size limits (max events counted
       simultaneously without multiplexing, for both all core counters
       and general-purpose counters only). The number of events accepted
       by perf_event_open() isn't reported as PMUs may accept groups that
       can never be scheduled.
     - Patch 34: User and interrupt sample register bitmasks
       (general-purpose and SIMD vector/predicate registers).
     - Patch 35: Interrupt registers that can only be sampled with
       precise sampling, such as x86 XMM registers captured by PEBS.
     - Patch 36: Memory profiling events (ldlat-loads, ldlat-stores,
       load-store) and supported memory sample formats.
     - Patch 37: User-space perf_event_mmap_page capabilities
       (cap_user_rdpmc, cap_user_time*, pmc_width, and live rdpmc
       verification).
     - Patch 38: Empirically measured branch instruction skid across
       precise_ip levels 0..3 using a noinline nop-runway loop.

4. 'perf sysinfo' subcommand, documentation, and test (Patches 39-41):
   Determining what performance monitoring hardware, driver capabilities,
   and event/metric support are available on a system currently requires
   piecing together sysfs files under /sys/bus/event_source/devices/,
   perf list output, and trial-and-error perf_event_open() calls. Across
   architectures and SoCs, support varies widely: an advanced system
   provides core PMUs with zero-skid precise event sampling
   (PEBS/IBS/SPE), hardware branch stack recording (LBR/BRBE),
   user-space counter reading (rdpmc), memory profiling (DATA_SRC),
   hardware AUX trace PMUs, memory and PCIe/IO interconnect bandwidth
   PMUs, and multi-level pipeline metric drill-down hierarchies, whereas
   a minimal system may only offer software timer sampling.

   - Patch 39 adds the 'perf sysinfo' subcommand. By default,
     'perf sysinfo' outputs a colorized system capability summary,
     pipeline metric drill-down hierarchy (via <metric>_group chains),
     overall system rating (Advanced, Standard, Basic, Minimal), and any
     missing baseline features. Passing '-p'/'--pmus' (or a PMU glob
     filter, '-v'/'--verbose', '-a'/'--all', or '-j'/'--json') displays
     detailed per-PMU static and probed attributes.
   - Patch 40 adds the 'perf-sysinfo' man page documentation.
   - Patch 41 adds the 'perf sysinfo' shell test.

Changes since v2 RFC
====================
- Dropped RFC status and restarted numbering at v1 as the series expanded
  to include per-perf_env PMU management and recorded metric reporting.
- Added thread-safe lazy perf_env initialization and the host_env()
  singleton (Patches 1-2).
- Encapsulated global PMU lists and scan state into struct perf_pmus
  embedded in struct perf_env and updated PMU find/scan callers
  (Patches 4-6).
- Unified strcmp_cpuid_str() across all architectures in
  tools/perf/util/header.c, selecting the comparison by the ELF machine
  of the CPUIDs, which is recorded for each pmu-events table, and made
  the CPUID matching test architecture independent (Patches 7-12).
- Added recorded-event metric matching and shadow counter evaluation in
  util/metricgroup.c, enabled metric reporting in 'perf stat report' and
  'perf script' (with --all-metrics and stat.all-metrics), fixed
  'perf script -F +metric' sample-based metrics, and added a shell test
  (Patches 14-25).
- Split the two perf_pmu__probe() commits into 10 per-capability commits
  (Patches 28-38) and moved the 'perf sysinfo' commits to the end of the
  series (Patches 39-41).

Example outputs
===============

Example default 'perf sysinfo' output on an Intel AlderLake hybrid system:

  $ perf sysinfo
  Summary:
    Core PMUs (2, hybrid): cpu_atom, cpu_core
    Core features:
      Interrupt-based sampling: supported
      Precise event sampling: supported (max_precise: 3 [3 - SAMPLE_IP must have 0 skid, or uses randomization])
      Branch stack sampling (LBR/BRBE): supported (depth: 32 branches)
      User-space counter reading (rdpmc): supported
      Leader sampling: supported
      Memory profiling events (DATA_SRC): supported
      Hardware AUX trace PMUs: intel_bts, intel_pt
    Bus & uncore monitoring:
      Memory bandwidth PMUs: uncore_imc, uncore_imc_free_running
      PCIe / IO interconnect bandwidth PMUs: uncore_arb
    Events & metrics:
      Available events: 3161 total (120 sysfs, 3041 json) across 28 PMUs
      Available metrics: 438 metrics in 192 groups (advanced)
      Metric drill-down levels (6): L1 (TopdownL1: 7 metrics), L2 (TopdownL2: 14 metrics), L3 (TopdownL3: 44 metrics), L4 (TopdownL4: 45 metrics), L5 (TopdownL5: 15 metrics), L6 (TopdownL6: 9 metrics)
    System rating: Advanced (core PMU with precise sampling, branch stack (LBR), user rdpmc, bus PMUs, and metric drill-down)

Example filtered per-PMU output:

  $ perf sysinfo cpu_core
  cpu_core (type: 4 [PERF_TYPE_RAW], core):
    CPUs: 0-15 (16 CPUs)
    Capabilities: branches=32, max_precise=3, pmu_name=alderlake_hybrid
    Formats: event (config:0-7), pc (config:19), edge (config:18), offcore_rsp (config1:0-63), ldlat (config1:0-15), inv (config:23), umask (config:8-15), frontend (config1:0-23), cmask (config:24-31)
    Events: 1563 total (20 sysfs, 1543 json)
    Memory events: ldlat-loads ({cpu_core/mem-loads-aux/,cpu_core/mem-loads,ldlat=30/}:P), ldlat-stores (cpu_core/mem-stores/P)
    Probed capabilities (event: cpu_core/cycles/):
      Modes: counting, sampling, branch_stack
      Precise sampling: :p (1 - SAMPLE_IP must have constant skid), :pp (2 - SAMPLE_IP requested to have 0 skid), :ppp (3 - SAMPLE_IP must have 0 skid, or uses randomization) (max_precise: 3)
      Measured branch skid (instructions from branch):
        level 0 (no :p): min 105, max 174, avg 156.8 (0/32 exact_ip)
        level 1 (:p)   : min 1, max 1, avg 1.0 (0/31 exact_ip)
        level 2 (:pp)  : min 0, max 0, avg 0.0 (31/31 exact_ip)
        level 3 (:ppp) : min 0, max 0, avg 0.0 (32/32 exact_ip)
      Sample types: ip, tid, time, addr, read, callchain, id, cpu, period, stream_id, raw, branch_stack, regs_user, stack_user, weight, data_src, identifier, transaction, regs_intr, aux, cgroup, data_page_size, code_page_size, weight_struct
      User registers: AX, BX, CX, DX, SI, DI, BP, SP, IP, FLAGS, CS, SS, R8, R9, R10, R11, R12, R13, R14, R15
      Intr registers: AX, BX, CX, DX, SI, DI, BP, SP, IP, FLAGS, CS, SS, R8, R9, R10, R11, R12, R13, R14, R15
      Intr registers (precise sampling only): XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15
      Memory events (probed): ldlat-loads, ldlat-stores (sample types: addr, weight, data_src, transaction, data_page_size, weight_struct)
      Exclusion modifiers: exclude_user, exclude_kernel, exclude_hv, exclude_idle, exclude_host, exclude_guest
      Features: leader_sampling, sw_grouping, inherit, sample_freq, write_backward, callchain, data_src, weight, pinned, exclusive, format_lost, user_rdpmc, user_time, user_time_zero
      User space counter reading (rdpmc): available, enabled, working (width: 48 bits)
      Max event group size: 8
      Max GP event group size: 7

Example output on a Raspberry Pi 5 (ARM Cortex-A76):

  $ sudo perf sysinfo armv8_cortex_a76
  armv8_cortex_a76 (type: 9 [dynamic], core):
    CPUs: 0-3 (4 CPUs)
    Capabilities: slots=0x00000000, threshold_max=0x00000000, bus_slots=0x00000000, bus_width=0x00000000
    Formats: threshold_count (config1:2), event (config:0-15), long (config1:0), rdpmc (config1:1), threshold (config1:5-16), threshold_compare (config1:3-4)
    Events: 1336 total (40 sysfs, 1296 json)
    Probed capabilities (event: armv8_cortex_a76/cycles/):
      Modes: counting, sampling
      Precise sampling: not supported (0 - SAMPLE_IP can have arbitrary skid)
      Measured branch skid (instructions from branch):
        level 0 (no :p): min 340, max 352, avg 346.2 (0/31 exact_ip)
      Sample types: ip, tid, time, addr, read, callchain, id, cpu, period, stream_id, raw, regs_user, stack_user, weight, data_src, identifier, transaction, regs_intr, phys_addr, aux, cgroup, data_page_size, code_page_size, weight_struct
      User registers: x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, lr, sp, pc
      Intr registers: x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, lr, sp, pc
      Exclusion modifiers: exclude_user, exclude_kernel, exclude_hv, exclude_host, exclude_guest
      Features: leader_sampling, sw_grouping, inherit, sample_freq, write_backward, callchain, data_src, weight, pinned, exclusive, format_lost, user_time, user_time_zero, user_time_short
      User space counter reading (rdpmc): available, disabled (sysfs: 0) (width: 32 bits)
      Max event group size: 7
      Max GP event group size: 6

  $ sudo perf sysinfo
  Summary:
    Core PMUs (1): armv8_cortex_a76
    Core features:
      Interrupt-based sampling: supported
      Precise event sampling: missing (0 - SAMPLE_IP can have arbitrary skid)
      Branch stack sampling (LBR/BRBE): missing
      User-space counter reading (rdpmc): missing/disabled
      Leader sampling: supported
      Memory profiling events (DATA_SRC): missing
      Hardware AUX trace PMUs: missing
    Bus & uncore monitoring:
      Memory bandwidth PMUs: missing
      PCIe / IO interconnect bandwidth PMUs: missing
    Events & metrics:
      Available events: 3695 total (2371 sysfs, 1324 json) across 11 PMUs
      Available metrics: 21 metrics in 5 groups (minimal)
      Metric drill-down levels: none (flat metrics only)
    System rating: Basic (core PMU with basic counting/sampling only; missing advanced features)
    Missing baseline features: PCIe/IO interconnect bandwidth PMUs, hardware AUX trace PMU, hardware branch stack sampling (LBR/BRBE), memory bus bandwidth PMUs, memory load/store profiling events, multi-level pipeline drill-down metrics (<metric>_group), precise event sampling (PEBS/IBS/SPE), user-space counter reading (rdpmc)

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

Ian Rogers (41):
  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 pmus: Add perf_pmus__read_from_env()
  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: Improve matching of metrics to recorded events
  perf metricgroup: Add metricgroup__add_recorded_metrics()
  perf script: Fix metrics computed from samples
  perf script: Don't drop metric samples for CPUs missing from the evsel
  perf stat report: Display metrics for recorded events
  perf script: Display metrics for perf stat record data
  perf test: Add stat record metrics test
  perf pmus: Expose duplicate uncore PMU scanning helpers
  perf mem-events: Expose PMU init and name formatting helpers
  perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling
  perf pmu: Probe supported sample_type flags
  perf pmu: Probe precise_ip levels
  perf pmu: Probe privilege exclusion modifiers
  perf pmu: Probe execution and AUX area features
  perf pmu: Probe event group size limits
  perf pmu: Probe user and interrupt sample register masks
  perf pmu: Probe interrupt registers that require precise sampling
  perf pmu: Probe memory profiling events and sample formats
  perf pmu: Probe user-space mmap capabilities
  perf pmu: Probe branch instruction skid
  perf sysinfo: Add sysinfo subcommand
  perf docs: Add perf-sysinfo man page
  perf test: Add perf sysinfo shell test

 tools/perf/Build                              |    1 +
 tools/perf/Documentation/perf-config.txt      |    7 +
 tools/perf/Documentation/perf-script.txt      |    6 +
 tools/perf/Documentation/perf-stat.txt        |    5 +
 tools/perf/Documentation/perf-sysinfo.txt     |  113 +
 tools/perf/Documentation/perf.txt             |    2 +-
 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-help.c                     |    3 +-
 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                   |  309 +--
 tools/perf/builtin-stat.c                     |   63 +-
 tools/perf/builtin-sysinfo.c                  | 2022 ++++++++++++++
 tools/perf/builtin-top.c                      |   17 +-
 tools/perf/builtin-trace.c                    |   18 +-
 tools/perf/builtin.h                          |    1 +
 tools/perf/perf.c                             |    4 +
 tools/perf/pmu-events/empty-pmu-events.c      |   34 +-
 tools/perf/pmu-events/jevents.py              |   45 +-
 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/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                |  116 +
 tools/perf/tests/shell/sysinfo.sh             |   83 +
 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                     |   14 +-
 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                        |    3 +-
 tools/perf/util/header.c                      |  203 +-
 tools/perf/util/header.h                      |    6 +-
 tools/perf/util/hwmon_pmu.c                   |   14 +-
 .../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                  |   17 +-
 tools/perf/util/mem-events.h                  |   20 +
 tools/perf/util/metricgroup.c                 | 1128 +++++++-
 tools/perf/util/metricgroup.h                 |   94 +-
 tools/perf/util/parse-events.c                |  179 +-
 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                         | 2312 ++++++++++++++++-
 tools/perf/util/pmu.h                         |  223 +-
 tools/perf/util/pmus.c                        |  564 +++-
 tools/perf/util/pmus.h                        |  283 +-
 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 +-
 110 files changed, 8192 insertions(+), 1063 deletions(-)
 create mode 100644 tools/perf/Documentation/perf-sysinfo.txt
 delete mode 100644 tools/perf/arch/arm64/tests/cpuid-match.c
 create mode 100644 tools/perf/builtin-sysinfo.c
 create mode 100644 tools/perf/tests/cpuid-match.c
 create mode 100755 tools/perf/tests/shell/sysinfo.sh

-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 01/41] perf env: Make lazy perf_env initialization thread-safe
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 02/41] perf env: Add host_env() singleton Ian Rogers
                   ` (39 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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.0.385.gac671c819a-goog


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

* [PATCH v1 02/41] perf env: Add host_env() singleton
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 01/41] perf env: Make lazy perf_env initialization thread-safe Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 03/41] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers
                   ` (38 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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/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 +++++++++++--
 24 files changed, 129 insertions(+), 131 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/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 b644d95c6be9..ca7550aa124a 100644
--- a/tools/perf/tests/symbols.c
+++ b/tools/perf/tests/symbols.c
@@ -15,18 +15,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;
 	}
 
@@ -34,7 +31,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;
 	}
 
@@ -45,7 +41,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 076d167380f2..7fae861dc646 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.0.385.gac671c819a-goog


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

* [PATCH v1 03/41] perf pmu: Add from_env parameter to perf_pmu__init()
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 01/41] perf env: Make lazy perf_env initialization thread-safe Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 02/41] perf env: Add host_env() singleton Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 04/41] perf pmus: Introduce struct perf_pmus and perf_env__pmus() Ian Rogers
                   ` (37 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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().

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/tests/pmu-events.c |  4 ++--
 tools/perf/util/drm_pmu.c     | 14 ++++++++----
 tools/perf/util/hwmon_pmu.c   | 14 ++++++++----
 tools/perf/util/pmu.c         | 41 +++++++++++++++++++----------------
 tools/perf/util/pmu.h         | 13 ++++++++++-
 tools/perf/util/tool_pmu.c    |  2 +-
 6 files changed, 57 insertions(+), 31 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 b8badae7015c..cbbd5aafd24b 100644
--- a/tools/perf/util/drm_pmu.c
+++ b/tools/perf/util/drm_pmu.c
@@ -68,7 +68,7 @@ bool perf_pmu__is_drm(const struct perf_pmu *pmu)
 
 bool evsel__is_drm(const struct evsel *evsel)
 {
-	return perf_pmu__is_drm(evsel->pmu);
+	return perf_pmu__is_drm(evsel->pmu) && !evsel->pmu->from_env;
 }
 
 static struct drm_pmu *add_drm_pmu(struct list_head *pmus, char *line, size_t line_len)
@@ -116,7 +116,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;
 	}
@@ -658,11 +658,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..fab9f09d0253 100644
--- a/tools/perf/util/hwmon_pmu.c
+++ b/tools/perf/util/hwmon_pmu.c
@@ -132,7 +132,7 @@ bool perf_pmu__is_hwmon(const struct perf_pmu *pmu)
 
 bool evsel__is_hwmon(const struct evsel *evsel)
 {
-	return perf_pmu__is_hwmon(evsel->pmu);
+	return perf_pmu__is_hwmon(evsel->pmu) && !evsel->pmu->from_env;
 }
 
 static size_t hwmon_pmu__event_hashmap_hash(long key, void *ctx __maybe_unused)
@@ -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;
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.0.385.gac671c819a-goog


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

* [PATCH v1 04/41] perf pmus: Introduce struct perf_pmus and perf_env__pmus()
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (2 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 03/41] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 05/41] perf pmus: Pass struct perf_pmus to find helpers Ian Rogers
                   ` (36 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 13e8d7fa80af..0a4d52d989bc 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 d060fca8090f..34ee1db63b9c 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 779fe56fe8d0..c8d64573e4c0 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 1355b317f3ed..472d62ccfc8f 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,38 +484,38 @@ 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 ?: "");
 
 	if (use_core_pmus) {
-		list_for_each_entry_continue(pmu, &core_pmus, list) {
+		list_for_each_entry_continue(pmu, &pmus->core_pmus, list) {
 			int pmu_name_len = pmu_name_len_no_suffix(pmu->name ?: "");
 
 			if (last_pmu_name_len == pmu_name_len &&
@@ -475,9 +525,9 @@ static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu)
 			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 &&
@@ -489,27 +539,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. */
@@ -630,7 +681,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;
@@ -638,16 +690,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));
@@ -661,7 +713,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);
 	}
@@ -745,18 +797,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,
@@ -796,67 +849,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)) {
 		/*
@@ -867,7 +932,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;
 }
@@ -875,11 +940,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;
 }
@@ -889,21 +956,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.0.385.gac671c819a-goog


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

* [PATCH v1 05/41] perf pmus: Pass struct perf_pmus to find helpers
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (3 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 04/41] perf pmus: Introduce struct perf_pmus and perf_env__pmus() Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 06/41] perf pmus: Pass struct perf_pmus to scan helpers Ian Rogers
                   ` (35 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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                 | 50 ++++++++++++-------
 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, 175 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 34ee1db63b9c..4053a80d3ba7 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 c8d64573e4c0..4e1205c1d3b2 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,
@@ -739,6 +740,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 +795,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 +809,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 +829,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 +931,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 +951,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 +968,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 +982,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 +1038,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 +1057,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 +1073,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 +1089,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 +1101,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 +1132,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 +1147,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 +1169,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,
@@ -1410,6 +1422,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;
@@ -1417,7 +1430,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);
@@ -1616,6 +1629,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;
 };
@@ -1626,13 +1640,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 472d62ccfc8f..4ad715783165 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.
@@ -851,7 +841,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);
 }
@@ -914,14 +904,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)) {
 		/*
@@ -932,7 +922,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;
 }
@@ -951,9 +941,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.0.385.gac671c819a-goog


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

* [PATCH v1 06/41] perf pmus: Pass struct perf_pmus to scan helpers
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (4 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 05/41] perf pmus: Pass struct perf_pmus to find helpers Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 07/41] perf pmu-events: Add e_machine to struct pmu_events_map Ian Rogers
                   ` (34 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 4e1205c1d3b2..100f54338e8e 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -933,7 +933,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 4ad715783165..c3a695ebf2ba 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;
@@ -550,7 +541,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. */
@@ -683,9 +674,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;
@@ -795,9 +786,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 = {
@@ -851,7 +842,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;
@@ -864,7 +855,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 |
@@ -943,7 +934,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 60dbe2dad5eb..065742a7431b 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.0.385.gac671c819a-goog


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

* [PATCH v1 07/41] perf pmu-events: Add e_machine to struct pmu_events_map
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (5 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 06/41] perf pmus: Pass struct perf_pmus to scan helpers Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 08/41] perf pmu-events: Pass e_machine to find_core_*_table() Ian Rogers
                   ` (33 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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.0.385.gac671c819a-goog


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

* [PATCH v1 08/41] perf pmu-events: Pass e_machine to find_core_*_table()
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (6 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 07/41] perf pmu-events: Add e_machine to struct pmu_events_map Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 09/41] perf header: Pass e_machine to strcmp_cpuid_str() Ian Rogers
                   ` (32 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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.0.385.gac671c819a-goog


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

* [PATCH v1 09/41] perf header: Pass e_machine to strcmp_cpuid_str()
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (7 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 08/41] perf pmu-events: Pass e_machine to find_core_*_table() Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 10/41] perf header: Support CPUID matching across all architectures Ian Rogers
                   ` (31 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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.0.385.gac671c819a-goog


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

* [PATCH v1 10/41] perf header: Support CPUID matching across all architectures
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (8 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 09/41] perf header: Pass e_machine to strcmp_cpuid_str() Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 11/41] perf test: Make the CPUID matching test architecture independent Ian Rogers
                   ` (30 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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.0.385.gac671c819a-goog


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

* [PATCH v1 11/41] perf test: Make the CPUID matching test architecture independent
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (9 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 10/41] perf header: Support CPUID matching across all architectures Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 12/41] perf test: Add CPUID matching tests for other architectures Ian Rogers
                   ` (29 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 58858f3d2bf9..4a45b6740050 100644
--- a/tools/perf/tests/Build
+++ b/tools/perf/tests/Build
@@ -44,6 +44,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 0b8897fd42ca..67d162ec9ef2 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -177,6 +177,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 20ae3609892b..0d54d1d5acd2 100644
--- a/tools/perf/tests/tests.h
+++ b/tools/perf/tests/tests.h
@@ -201,6 +201,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.0.385.gac671c819a-goog


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

* [PATCH v1 12/41] perf test: Add CPUID matching tests for other architectures
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (10 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 11/41] perf test: Make the CPUID matching test architecture independent Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 13/41] perf pmus: Add perf_pmus__read_from_env() Ian Rogers
                   ` (28 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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.0.385.gac671c819a-goog


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

* [PATCH v1 13/41] perf pmus: Add perf_pmus__read_from_env()
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (11 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 12/41] perf test: Add CPUID matching tests for other architectures Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 14/41] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric() Ian Rogers
                   ` (27 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 |  22 ++++
 tools/perf/util/pmu.c    |  31 ++++++
 tools/perf/util/pmu.h    |   9 ++
 tools/perf/util/pmus.c   | 219 +++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmus.h   |  15 ++-
 7 files changed, 318 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..27fc21fa3a6a 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -5765,6 +5765,28 @@ 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:
+		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 06c4912d2f65..ee52fd18c927 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -2570,6 +2570,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 7605c3859337..06f922ce1f68 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 c3a695ebf2ba..908959abbd3d 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -7,7 +7,9 @@
 #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>
@@ -980,3 +982,220 @@ 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 || !strcmp(perf_env__arch(env), perf_env__arch(host_env())))
+		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 (pmu) {
+		if (pmu->type == PERF_PMU_TYPE_FAKE && type != PERF_PMU_TYPE_FAKE) {
+			pmu->type = type;
+			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;
+			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.0.385.gac671c819a-goog


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

* [PATCH v1 14/41] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric()
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (12 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 13/41] perf pmus: Add perf_pmus__read_from_env() Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 15/41] perf metricgroup: Move matching of metrics to recorded events from perf script Ian Rogers
                   ` (26 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 b6691ebb1b4e..8bef76eaf67d 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2528,6 +2528,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,
@@ -2546,7 +2547,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 100f54338e8e..876c549069e5 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,
 	};
@@ -1117,7 +1119,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;
 
@@ -1653,7 +1655,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.0.385.gac671c819a-goog


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

* [PATCH v1 15/41] perf metricgroup: Move matching of metrics to recorded events from perf script
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (13 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 14/41] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric() Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 16/41] perf parse-events: Decode metric-id terms when adding events Ian Rogers
                   ` (25 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 8bef76eaf67d..da3f1793774e 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2354,151 +2354,6 @@ static void script_new_line(struct perf_stat_config *config __maybe_unused,
 	fputs("\tmetric: ", mctx->fp);
 }
 
-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)
 {
@@ -2528,12 +2383,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;
 
@@ -2547,8 +2396,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 876c549069e5..51dbdeac5e60 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.0.385.gac671c819a-goog


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

* [PATCH v1 16/41] perf parse-events: Decode metric-id terms when adding events
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (14 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 15/41] perf metricgroup: Move matching of metrics to recorded events from perf script Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 17/41] perf evsel: Generalize metric_id to a set of metric_ids Ian Rogers
                   ` (24 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 |  74 +++++++++++++++++++++-
 tools/perf/util/parse-events.h |   1 +
 3 files changed, 101 insertions(+), 84 deletions(-)

diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 51dbdeac5e60..b4a978794cc8 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);
@@ -1513,7 +1457,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..1c02c2b90684 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;
 	}
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.0.385.gac671c819a-goog


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

* [PATCH v1 17/41] perf evsel: Generalize metric_id to a set of metric_ids
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (15 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 16/41] perf parse-events: Decode metric-id terms when adding events Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 18/41] perf pmu: Add perf_pmu__for_each_name_from_config() Ian Rogers
                   ` (23 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 4053a80d3ba7..e73a84e6d59d 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 b4a978794cc8..e87da791fd1e 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, '@')) {
@@ -1470,15 +1492,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 1c02c2b90684..1cda7dc4d00f 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 065742a7431b..cdd12557d065 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.0.385.gac671c819a-goog


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

* [PATCH v1 18/41] perf pmu: Add perf_pmu__for_each_name_from_config()
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (16 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 17/41] perf evsel: Generalize metric_id to a set of metric_ids Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 19/41] perf metricgroup: Improve matching of metrics to recorded events Ian Rogers
                   ` (22 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 ee52fd18c927..fecfe791bf8a 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -2840,25 +2840,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 06f922ce1f68..b556ec51fdeb 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -374,6 +374,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.0.385.gac671c819a-goog


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

* [PATCH v1 19/41] perf metricgroup: Improve matching of metrics to recorded events
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (17 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 18/41] perf pmu: Add perf_pmu__for_each_name_from_config() Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 20/41] perf metricgroup: Add metricgroup__add_recorded_metrics() Ian Rogers
                   ` (21 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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. Compare events with a key of the PMU name,
   counter encoding and exclude modifiers, converting legacy hardware
   events of core PMUs to the equivalent sysfs event's encoding.

 - 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, counts, and
   group membership.

Also avoid adding duplicate metrics 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 | 644 ++++++++++++++++++++++++++++------
 tools/perf/util/metricgroup.h |   5 +-
 2 files changed, 547 insertions(+), 102 deletions(-)

diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index e87da791fd1e..90d22c67bfae 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);
 }
@@ -379,6 +388,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,151 +483,581 @@ 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 match 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;
+	/** @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;
+	/** @exclude: perf_event_attr exclude bits, a mask of COUNTER_KEY_EXCLUDE_*. */
+	unsigned int exclude;
+};
+
+#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_PRIV	(COUNTER_KEY_EXCLUDE_USER | COUNTER_KEY_EXCLUDE_KERNEL | \
+					 COUNTER_KEY_EXCLUDE_HV)
+
+/* 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 find_matching_metrics_args {
 	struct evlist *evlist;
+	/** @keys: Counter keys of evlist's evsels, indexed by evsel->core.idx. */
+	struct counter_key *keys;
+	/** @nr_keys: Number of entries in keys. */
+	int nr_keys;
+	/** @legacy_hw_keys: Cache of legacy_hw_key. */
+	struct list_head legacy_hw_keys;
 	bool system_wide;
 };
 
-static struct evsel *map_metric_evsel_to_evlist(struct evlist *data_evlist,
+static void counter_key__init_attr(struct counter_key *key, const char *pmu_name,
+				   const struct perf_event_attr *attr)
+{
+	key->pmu_name = pmu_name;
+	key->type = attr->type;
+	key->config = attr->config;
+	key->config1 = attr->config1;
+	key->config2 = attr->config2;
+	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);
+	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)
+		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;
+}
+
+/*
+ * 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 find_matching_metrics_args *args, 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, &args->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, &args->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__counter_key(struct find_matching_metrics_args *args, 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);
+	if (key->type == PERF_TYPE_HARDWARE && pmu && pmu->is_core)
+		legacy_hw_key(args, pmu->name, key->config, key);
+}
+
+static bool counter_key__equal(const struct counter_key *a, const struct counter_key *b)
+{
+	unsigned int a_priv, b_priv;
+
+	if (a->type != b->type || a->config != b->config ||
+	    a->config1 != b->config1 || a->config2 != b->config2)
+		return false;
+
+	/*
+	 * 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) must match. A recorded event may count the
+	 * guest, when the metric event doesn't, if exclude_guest was cleared
+	 * by evsel__fallback() for kernels without support for it, but not the
+	 * other way around.
+	 */
+	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->pmu_name && b->pmu_name)
+		return !strcmp(a->pmu_name, b->pmu_name);
+
+	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;
+}
+
+/*
+ * Find the evsel in the data evlist programming the same counter as
+ * @metric_evsel. The same counter may be recorded more than once, such as when
+ * different metrics were measured in different groups. Prefer an evsel with the
+ * same name, as the metric would have been parsed to when recording, and an
+ * evsel with counts, over other evsels.
+ */
+static struct evsel *map_metric_evsel_to_evlist(struct find_matching_metrics_args *args,
 						struct evsel *metric_evsel)
 {
-	struct evsel *data_evsel;
+	struct evsel *data_evsel, *best = NULL;
+	struct counter_key metric_key;
+	bool metric_grouped = evsel__leader(metric_evsel)->core.nr_members > 1;
+	int best_score = -1;
+
+	evsel__counter_key(args, metric_evsel, &metric_key);
+	evlist__for_each_entry(args->evlist, data_evsel) {
+		bool data_grouped;
+		int score;
 
-	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)
+		if (!data_evsel->supported)
 			continue;
-		if (metric_evsel->core.attr.config != data_evsel->core.attr.config)
+
+		/* Skip if the evsels don't program the same counter. */
+		if (data_evsel->core.idx >= args->nr_keys ||
+		    !counter_key__equal(&metric_key, &args->keys[data_evsel->core.idx]))
 			continue;
-		return data_evsel;
+
+		data_grouped = evsel__leader(data_evsel)->core.nr_members > 1 ||
+			       (data_evsel->core.attr.read_format & PERF_FORMAT_GROUP);
+		score = (!strcmp(evsel__name(metric_evsel), evsel__name(data_evsel)) ? 16 : 0) +
+			(metric_key.exclude == args->keys[data_evsel->core.idx].exclude ? 8 : 0) +
+			evsel__count_score(data_evsel) * 2 +
+			(metric_grouped == data_grouped ? 1 : 0);
+		if (score > best_score) {
+			best = data_evsel;
+			best_score = score;
+		}
 	}
+	return best;
+}
+
+/*
+ * 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 *pos;
+
+	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;
+	}
+	if (ev->pmu && ev->pmu->is_core && pmu && pmu->is_core)
+		return NULL;
+	return ev;
+}
+
+/*
+ * 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 metric_expr *copy = zalloc(sizeof(*copy));
+	int nr = 0;
+
+	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;
+
+	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;
+		copy->metric_events[i] = map_metric_evsel_to_evlist(args, ev);
+		if (!copy->metric_events[i])
+			goto err;
+		if (evsel__copy_metric_ids(copy->metric_events[i], expr->metric_events[i]))
+			goto err;
+		/*
+		 * As in setup_metric_events(), a matched event without a metric
+		 * leader leads itself so that evsel__source_count() counts it.
+		 */
+		if (!copy->metric_events[i]->metric_leader)
+			copy->metric_events[i]->metric_leader = copy->metric_events[i];
+	}
+	return copy;
+err:
+	metric_expr__delete(copy);
 	return NULL;
 }
 
+/* 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;
+		}
+	}
+	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 =
+					map_metric_evsel_to_evlist(args, metric_evsel);
+
+				if (data_evsel)
+					data_evsel->default_show_events = true;
+			}
+		}
+	}
+
+	/*
+	 * 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 metric_event *metric_me =
+			metricgroup__lookup(evlist__metric_events(metric_evlist),
+					    metric_evsel,
+					    /*create=*/false);
+		struct metric_expr *expr;
+		struct evsel *pos;
+
+		if (!metric_me || metric_me->evsel != metric_evsel)
+			continue;
+
+		evlist__for_each_entry(metric_evlist, pos) {
+			struct evsel *data_evsel;
+
+			if (pos != metric_evsel &&
+			    (pos->metric_leader != metric_evsel ||
+			     !evsel__shares_metric_id(pos, metric_evsel)))
+				continue;
+
+			data_evsel = map_metric_evsel_to_evlist(args, pos);
+			if (!data_evsel)
+				continue;
+
+			list_for_each_entry(expr, &metric_me->head, nd) {
+				struct metric_event *data_me;
+				struct metric_expr *copy =
+					metric_expr__dup_for_data(args, metric_evlist, expr,
+								  pos->pmu);
+
+				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;
+				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;
+}
+
 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,
+	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=*/false,
+					/*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;
 	}
 
-	/*
-	 * 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.
-	 */
+	/* Check the data_evlist has an entry for each metric_evlist entry. */
 	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) {
+		if (!map_metric_evsel_to_evlist(args, metric_evsel)) {
 			pr_debug("Skipping metric '%s' as evsel '%s' is missing\n",
-				pm->metric_name, evsel__name(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__name(metric_evsel),
-				evsel__name(data_evsel));
+				 pm->metric_name, evsel__name(metric_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 (evsel__copy_metric_ids(data_evsel, metric_evsel))
-			goto out;
+	ret = add_matching_metric_evlist(args, metric_evlist,
+					 /*default_metricgroup=*/false);
+out:
+	evlist__put(metric_evlist);
+	return ret;
+}
 
-		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);
-			}
-		}
+static int find_matching_metrics_args__init(struct find_matching_metrics_args *args,
+					    struct evlist *evlist, bool system_wide)
+{
+	struct evsel *evsel;
+
+	memset(args, 0, sizeof(*args));
+	args->evlist = evlist;
+	args->system_wide = system_wide;
+	INIT_LIST_HEAD(&args->legacy_hw_keys);
+	args->nr_keys = evlist__nr_entries(evlist);
+	args->keys = calloc(args->nr_keys, sizeof(*args->keys));
+	if (!args->keys)
+		return -ENOMEM;
 
-		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]);
-				}
-			}
-		}
+	evlist__for_each_entry(evlist, evsel) {
+		if (evsel->core.idx < args->nr_keys)
+			evsel__counter_key(args, evsel, &args->keys[evsel->core.idx]);
 	}
-	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 void find_matching_metrics_args__exit(struct find_matching_metrics_args *args)
+{
+	struct legacy_hw_key *pos, *tmp;
+
+	list_for_each_entry_safe(pos, tmp, &args->legacy_hw_keys, list) {
+		list_del(&pos->list);
+		free(pos->pmu_name);
+		free(pos);
+	}
+	zfree(&args->keys);
+}
+
 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;
+
+	ret = find_matching_metrics_args__init(&args, evlist, system_wide);
+	if (ret)
+		return ret;
+
+	ret = metricgroup__for_each_metric(perf_env__pmus(env), pmu_metrics_table__find(),
+					   find_matching_metrics_cb, &args);
 
-	return metricgroup__for_each_metric(perf_env__pmus(env), pmu_metrics_table__find(),
-					    find_matching_metrics_cb, &args);
+	find_matching_metrics_args__exit(&args);
+	return ret;
 }
 
 static int append_metric_id_cb(const char *metric_id, void *data)
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.0.385.gac671c819a-goog


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

* [PATCH v1 20/41] perf metricgroup: Add metricgroup__add_recorded_metrics()
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (18 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 19/41] perf metricgroup: Improve matching of metrics to recorded events Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 21/41] perf script: Fix metrics computed from samples Ian Rogers
                   ` (20 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 | 311 ++++++++++++++++++++++++++++++++++
 tools/perf/util/metricgroup.h |  61 +++++++
 2 files changed, 372 insertions(+)

diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 90d22c67bfae..668106d3e08f 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"
@@ -1060,6 +1062,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;
+	int ret;
+
+	ret = find_matching_metrics_args__init(&args, evlist, system_wide);
+	if (ret)
+		return ret;
+
+	metric_evlist = evlist__new();
+	if (!metric_evlist) {
+		find_matching_metrics_args__exit(&args);
+		return -ENOMEM;
+	}
+	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);
+	}
+
+	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;
+				if (!all_metrics)
+					evsel->default_metricgroup = 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..7b371171d315 100644
--- a/tools/perf/util/metricgroup.h
+++ b/tools/perf/util/metricgroup.h
@@ -112,6 +112,67 @@ 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 any metric or metricgroup in
+ *                                       @metric_or_groups exists for @pmus.
+ * @pmus: The PMUs to search.
+ * @pmu: PMU filter name or "all".
+ * @metric_or_groups: Comma-separated list of metric or metricgroup names.
+ *
+ * Return: true if all names in @metric_or_groups match a metric or group.
+ */
 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.0.385.gac671c819a-goog


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

* [PATCH v1 21/41] perf script: Fix metrics computed from samples
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (19 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 20/41] perf metricgroup: Add metricgroup__add_recorded_metrics() Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 22/41] perf script: Don't drop metric samples for CPUs missing from the evsel Ian Rogers
                   ` (19 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 da3f1793774e..199a5af81f37 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(). */
 }
 
 static struct aggr_cpu_id script_aggr_cpu_id_get(struct perf_stat_config *config __maybe_unused,
@@ -2379,7 +2375,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. */
@@ -2445,14 +2440,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.0.385.gac671c819a-goog


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

* [PATCH v1 22/41] perf script: Don't drop metric samples for CPUs missing from the evsel
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (20 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 21/41] perf script: Fix metrics computed from samples Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 23/41] perf stat report: Display metrics for recorded events Ian Rogers
                   ` (18 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 199a5af81f37..afed2353ef80 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2409,15 +2409,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.0.385.gac671c819a-goog


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

* [PATCH v1 23/41] perf stat report: Display metrics for recorded events
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (21 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 22/41] perf script: Don't drop metric samples for CPUs missing from the evsel Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 24/41] perf script: Display metrics for perf stat record data Ian Rogers
                   ` (17 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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                | 53 +++++++++++++++++++++---
 tools/perf/util/config.c                 |  3 ++
 tools/perf/util/stat.h                   |  1 +
 5 files changed, 64 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 f81192cec481..12004244af0e 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -2232,6 +2232,28 @@ 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__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();
 
@@ -2282,6 +2304,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;
 
@@ -2293,6 +2317,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;
 }
@@ -2351,7 +2378,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,
@@ -2367,11 +2394,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);
@@ -2398,8 +2434,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;
@@ -2408,10 +2446,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)
@@ -2589,6 +2630,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.0.385.gac671c819a-goog


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

* [PATCH v1 24/41] perf script: Display metrics for perf stat record data
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (22 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 23/41] perf stat report: Display metrics for recorded events Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 25/41] perf test: Add stat record metrics test Ian Rogers
                   ` (16 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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              | 106 ++++++++++++++++++++++-
 2 files changed, 109 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..81e2def8420f 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,111 @@ 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__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 +3913,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 +4303,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 +4676,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.0.385.gac671c819a-goog


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

* [PATCH v1 25/41] perf test: Add stat record metrics test
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (23 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 24/41] perf script: Display metrics for perf stat record data Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 26/41] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers
                   ` (15 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

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 | 116 +++++++++++++++++++++++++++++++++
 1 file changed, 116 insertions(+)

diff --git a/tools/perf/tests/shell/stat.sh b/tools/perf/tests/shell/stat.sh
index ac9e031831da..bc7d29ec7b0f 100755
--- a/tools/perf/tests/shell/stat.sh
+++ b/tools/perf/tests/shell/stat.sh
@@ -92,6 +92,121 @@ 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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}" "${stat_config}"
+    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]"
+    rm -f "${stat_data}" "${stat_config}"
+    err=1
+    return
+  fi
+  rm -f "${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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}"
+    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]"
+    rm -f "${stat_data}"
+    err=1
+    return
+  fi
+  rm -f "${stat_data}"
+  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 +716,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.0.385.gac671c819a-goog


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

* [PATCH v1 26/41] perf pmus: Expose duplicate uncore PMU scanning helpers
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (24 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 25/41] perf test: Add stat record metrics test Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 27/41] perf mem-events: Expose PMU init and name formatting helpers Ian Rogers
                   ` (14 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Export perf_pmus__scan_skip_duplicates() and add
perf_pmu__num_duplicates() to count numbered PMU instances sharing a
common prefix (such as uncore_imc_0..uncore_imc_7) so subcommands like
'perf sysinfo' can collapse duplicate uncore PMUs by default and report
their instance count.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmus.c | 25 ++++++++++++++++++++++++-
 tools/perf/util/pmus.h | 19 +++++++++++++++++++
 2 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
index 908959abbd3d..aa088c9c9aec 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -484,7 +484,7 @@ struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmus *pmus,
 	return NULL;
 }
 
-static struct perf_pmu *
+struct perf_pmu *
 perf_pmus__scan_skip_duplicates(struct perf_pmus *pmus, struct perf_pmu *pmu)
 {
 	bool use_core_pmus = !pmu || pmu->is_core;
@@ -522,6 +522,29 @@ perf_pmus__scan_skip_duplicates(struct perf_pmus *pmus, struct perf_pmu *pmu)
 	return NULL;
 }
 
+size_t perf_pmu__num_duplicates(const struct perf_pmu *pmu)
+{
+	const struct list_head *head;
+	const struct perf_pmu *pos = pmu;
+	size_t base_len;
+	size_t count = 1;
+
+	if (!pmu || !pmu->pmus)
+		return 1;
+
+	head = pmu->is_core ? &pmu->pmus->core_pmus : &pmu->pmus->other_pmus;
+	base_len = pmu_name_len_no_suffix(pmu->name ?: "");
+	list_for_each_entry_continue(pos, head, list) {
+		size_t pos_len = pmu_name_len_no_suffix(pos->name ?: "");
+
+		if (pos_len != base_len ||
+		    strncmp(pmu->name ?: "", pos->name ?: "", base_len))
+			break;
+		count++;
+	}
+	return count;
+}
+
 struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmus *pmus,
 					       struct perf_pmu *pmu, const char *compat)
 {
diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h
index 2b784cf648f3..9790be2d60a5 100644
--- a/tools/perf/util/pmus.h
+++ b/tools/perf/util/pmus.h
@@ -134,6 +134,24 @@ struct perf_pmu *perf_pmus__scan(struct perf_pmus *pmus, struct perf_pmu *pmu);
  */
 struct perf_pmu *perf_pmus__scan_core(struct perf_pmus *pmus, struct perf_pmu *pmu);
 
+/**
+ * perf_pmus__scan_skip_duplicates() - Iterate over PMUs in @pmus, skipping
+ *                                     numbered duplicate instances.
+ * @pmus: The PMU collection to scan.
+ * @pmu: Previous PMU returned, or NULL to start from the first PMU.
+ *
+ * Return: Next distinct prefix struct perf_pmu, or NULL when done.
+ */
+struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmus *pmus, struct perf_pmu *pmu);
+
+/**
+ * perf_pmu__num_duplicates() - Count consecutive PMUs sharing @pmu's prefix.
+ * @pmu: Representative PMU returned by perf_pmus__scan_skip_duplicates().
+ *
+ * Return: Number of PMU instances (including @pmu) sharing its base name.
+ */
+size_t perf_pmu__num_duplicates(const struct perf_pmu *pmu);
+
 /**
  * perf_pmus__scan_for_event() - Iterate over PMUs in @pmus that have @event.
  * @pmus: The PMU collection to scan.
@@ -260,6 +278,7 @@ 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);
+
 /**
  * perf_pmus__find_core_pmu() - Return the first core PMU in @pmus.
  * @pmus: The PMU collection to search.
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 27/41] perf mem-events: Expose PMU init and name formatting helpers
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (25 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 26/41] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 28/41] perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling Ian Rogers
                   ` (13 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Export perf_pmu__mem_events_init_pmu() and perf_pmu__mem_events_name()
so callers can initialize and format per-PMU memory profiling events.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/mem-events.c | 12 ++++++------
 tools/perf/util/mem-events.h | 20 ++++++++++++++++++++
 2 files changed, 26 insertions(+), 6 deletions(-)

diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c
index 62543df0dca1..932efcfd82f7 100644
--- a/tools/perf/util/mem-events.c
+++ b/tools/perf/util/mem-events.c
@@ -82,12 +82,12 @@ int perf_pmu__mem_events_num_mem_pmus(struct perf_pmu *pmu)
 	return num;
 }
 
-static const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i,
-					     char *buf, size_t buf_size)
+const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i,
+				      char *buf, size_t buf_size)
 {
 	struct perf_mem_event *e;
 
-	if (i >= PERF_MEM_EVENTS__MAX || !pmu)
+	if (i >= PERF_MEM_EVENTS__MAX || !pmu || !pmu->mem_events)
 		return NULL;
 
 	e = &pmu->mem_events[i];
@@ -196,13 +196,13 @@ static bool perf_pmu__mem_events_supported(const char *mnt, struct perf_pmu *pmu
 	return !stat(path, &st);
 }
 
-static int __perf_pmu__mem_events_init(struct perf_pmu *pmu)
+int perf_pmu__mem_events_init_pmu(struct perf_pmu *pmu)
 {
 	const char *mnt = sysfs__mount();
 	bool found = false;
 	int j;
 
-	if (!mnt)
+	if (!mnt || !pmu || pmu->from_env || !pmu->mem_events)
 		return -ENOENT;
 
 	for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
@@ -228,7 +228,7 @@ int perf_pmu__mem_events_init(void)
 	struct perf_pmu *pmu = NULL;
 
 	while ((pmu = perf_pmus__scan_mem(pmu)) != NULL) {
-		if (__perf_pmu__mem_events_init(pmu))
+		if (perf_pmu__mem_events_init_pmu(pmu))
 			return -ENOENT;
 	}
 
diff --git a/tools/perf/util/mem-events.h b/tools/perf/util/mem-events.h
index 38b14c03392c..f2e622a048c8 100644
--- a/tools/perf/util/mem-events.h
+++ b/tools/perf/util/mem-events.h
@@ -31,8 +31,28 @@ extern struct perf_mem_event perf_mem_events[PERF_MEM_EVENTS__MAX];
 extern bool perf_mem_record[PERF_MEM_EVENTS__MAX];
 
 int perf_pmu__mem_events_parse(struct perf_pmu *pmu, const char *str);
+
+/**
+ * perf_pmu__mem_events_init_pmu() - Initialize memory event table for @pmu.
+ * @pmu: The PMU to check for memory event support.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int perf_pmu__mem_events_init_pmu(struct perf_pmu *pmu);
 int perf_pmu__mem_events_init(void);
 
+/**
+ * perf_pmu__mem_events_name() - Format the event string for a memory event.
+ * @pmu: The PMU providing the memory event.
+ * @i: Memory event index (PERF_MEM_EVENTS__LOAD, etc.).
+ * @buf: Output buffer to format the event string into.
+ * @buf_size: Size of @buf in bytes.
+ *
+ * Return: Pointer to @buf on success, or NULL if invalid/unsupported.
+ */
+const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i,
+				      char *buf, size_t buf_size);
+
 struct perf_mem_event *perf_pmu__mem_events_ptr(struct perf_pmu *pmu, int i);
 struct perf_pmu *perf_mem_events_find_pmu(void);
 int perf_pmu__mem_events_num_mem_pmus(struct perf_pmu *pmu);
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 28/41] perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (26 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 27/41] perf mem-events: Expose PMU init and name formatting helpers Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 29/41] perf pmu: Probe supported sample_type flags Ian Rogers
                   ` (12 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Sysfs PMU metadata does not expose many runtime kernel and hardware
capabilities, such as whether a PMU supports counting vs interrupt-based
sampling or whether perf_event_open() succeeds under the current
privilege level.

Add struct perf_pmu::probed and perf_pmu__probe() to actively probe a
live PMU via perf_event_open() on a dedicated worker thread pinned to an
online CPU associated with the PMU. Select a representative probe event
for tracepoint, kprobe, uprobe, core, software, breakpoint, and uncore
PMUs, hold a keepalive fd for tracepoints to avoid repeated RCU
synchronize_rcu() delays on close, and record whether opening, counting
(sample_period == 0), and sampling (sample_period > 0) are supported.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 562 ++++++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmu.h |  31 ++-
 2 files changed, 592 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index fecfe791bf8a..9033bada9f47 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -9,7 +9,9 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <errno.h>
+#include <limits.h>
 #include <stdbool.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -20,6 +22,7 @@
 #include <locale.h>
 #include <fnmatch.h>
 #include <math.h>
+#include <pthread.h>
 #include "debug.h"
 #include "evsel.h"
 #include "pmu.h"
@@ -38,7 +41,21 @@
 #include "strbuf.h"
 #include "fncache.h"
 #include "util/evsel_config.h"
+#include "cloexec.h"
+#include "dwarf-regs.h"
+#include "evlist.h"
+#include "mem-events.h"
+#include "mmap.h"
+#include "perf_regs.h"
+#include "rlimit.h"
+#include "util/sample.h"
+#include "../perf-sys.h"
+#include <linux/hw_breakpoint.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sched.h>
 #include <regex.h>
+#include <elf.h>
 
 #define UNIT_MAX_LEN	31 /* max length for event unit name */
 
@@ -2837,9 +2854,554 @@ void perf_pmu__delete(struct perf_pmu *pmu)
 	zfree(&pmu->name);
 	zfree(&pmu->alias_name);
 	zfree(&pmu->id);
+	zfree(&pmu->probed.probe_event);
 	free(pmu);
 }
 
+#define MAX_PROBE_CANDIDATES 16
+
+static unsigned long dummy_bp_var;
+
+/*
+ * Resolve /proc/self/exe and compute the file offset of its ELF entry point
+ * so the uprobe PMU can be probed against the running perf binary.
+ */
+static bool get_self_uprobe_target(char *exe_path, __u64 *out_offset)
+{
+#if __SIZEOF_POINTER__ == 8
+	Elf64_Ehdr ehdr;
+#else
+	Elf32_Ehdr ehdr;
+#endif
+	int fd;
+	bool found = false;
+
+	if (!realpath("/proc/self/exe", exe_path))
+		return false;
+
+	fd = open(exe_path, O_RDONLY | O_CLOEXEC);
+	if (fd < 0)
+		return false;
+
+	if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr) ||
+	    memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) {
+		close(fd);
+		return false;
+	}
+
+	for (unsigned int i = 0; i < ehdr.e_phnum; i++) {
+#if __SIZEOF_POINTER__ == 8
+		Elf64_Phdr phdr;
+#else
+		Elf32_Phdr phdr;
+#endif
+		off_t pos = (off_t)ehdr.e_phoff + (off_t)i * ehdr.e_phentsize;
+
+		if (ehdr.e_phentsize < sizeof(phdr))
+			break;
+
+		if (pread(fd, &phdr, sizeof(phdr), pos) != sizeof(phdr))
+			break;
+		if (phdr.p_type != PT_LOAD || !(phdr.p_flags & PF_X))
+			continue;
+		if (ehdr.e_entry >= phdr.p_vaddr &&
+		    ehdr.e_entry < phdr.p_vaddr + phdr.p_filesz) {
+			*out_offset = phdr.p_offset + (ehdr.e_entry - phdr.p_vaddr);
+			found = true;
+			break;
+		}
+	}
+
+	close(fd);
+	return found;
+}
+
+struct probe_candidates {
+	char *names[MAX_PROBE_CANDIDATES];
+	size_t nr;
+};
+
+static void probe_candidates__add(struct probe_candidates *cands, const char *str)
+{
+	if (cands->nr >= MAX_PROBE_CANDIDATES || !str)
+		return;
+	for (size_t i = 0; i < cands->nr; i++) {
+		if (!strcmp(cands->names[i], str))
+			return;
+	}
+	cands->names[cands->nr] = strdup(str);
+	if (cands->names[cands->nr])
+		cands->nr++;
+}
+
+static void probe_candidates__exit(struct probe_candidates *cands)
+{
+	for (size_t i = 0; i < cands->nr; i++)
+		zfree(&cands->names[i]);
+	cands->nr = 0;
+}
+
+static int collect_probe_candidate_cb(void *vstate, struct pmu_event_info *info)
+{
+	struct probe_candidates *cands = vstate;
+	char buf[512];
+
+	if (info->deprecated)
+		return 0;
+	if (info->str && strstr(info->str, "=?"))
+		return 0;
+	if (!info->name)
+		return 0;
+
+	if (info->pmu && info->pmu->type == PERF_TYPE_TRACEPOINT) {
+		probe_candidates__add(cands, info->name);
+		return 1;
+	} else if (strchr(info->name, '/')) {
+		probe_candidates__add(cands, info->name);
+	} else if (info->pmu && info->pmu->name) {
+		snprintf(buf, sizeof(buf), "%s/%s/", info->pmu->name, info->name);
+		probe_candidates__add(cands, buf);
+	} else {
+		probe_candidates__add(cands, info->name);
+	}
+
+	return cands->nr >= MAX_PROBE_CANDIDATES ? 1 : 0;
+}
+
+static bool parse_candidate_attr(const char *event_str, struct perf_event_attr *attr)
+{
+	struct evlist *evlist;
+	struct parse_events_error err;
+	struct evsel *evsel;
+	int ret;
+
+	evlist = evlist__new();
+	if (!evlist)
+		return false;
+
+	parse_events_error__init(&err);
+	ret = __parse_events(evlist, event_str, /*pmu_filter=*/NULL,
+			     /*cputype_filter=*/false, &err, /*fake_pmu=*/false,
+			     /*warn_if_reordered=*/false, /*fake_tp=*/false);
+	parse_events_error__exit(&err);
+	if (ret || evlist__empty(evlist)) {
+		evlist__put(evlist);
+		return false;
+	}
+
+	evsel = evlist__first(evlist);
+	*attr = evsel->core.attr;
+	evlist__put(evlist);
+	return true;
+}
+
+/*
+ * Pick an online CPU associated with @pmu (preferring one in the caller's
+ * CPU affinity mask) on which to run perf_event_open() probes.
+ */
+static int select_probe_cpu(const struct perf_pmu *pmu)
+{
+	struct perf_cpu_map *online = perf_cpu_map__new_online_cpus();
+	cpu_set_t aff;
+	bool have_aff;
+	int cpu = 0;
+
+	CPU_ZERO(&aff);
+	have_aff = sched_getaffinity(0, sizeof(aff), &aff) == 0;
+
+	if (pmu->cpus && !perf_cpu_map__is_empty(pmu->cpus)) {
+		int nr = perf_cpu_map__nr(pmu->cpus);
+		bool matched_online = false;
+
+		cpu = perf_cpu_map__cpu(pmu->cpus, 0).cpu;
+		if (online) {
+			for (int i = 0; i < nr; i++) {
+				struct perf_cpu c = perf_cpu_map__cpu(pmu->cpus, i);
+
+				if (!perf_cpu_map__has(online, c))
+					continue;
+				if (!matched_online) {
+					cpu = c.cpu;
+					matched_online = true;
+				}
+				if (have_aff && c.cpu >= 0 && c.cpu < CPU_SETSIZE &&
+				    CPU_ISSET(c.cpu, &aff)) {
+					cpu = c.cpu;
+					break;
+				}
+			}
+		}
+	} else if (online && !perf_cpu_map__is_empty(online)) {
+		int nr = perf_cpu_map__nr(online);
+
+		cpu = perf_cpu_map__cpu(online, 0).cpu;
+		if (have_aff) {
+			for (int i = 0; i < nr; i++) {
+				struct perf_cpu c = perf_cpu_map__cpu(online, i);
+
+				if (c.cpu >= 0 && c.cpu < CPU_SETSIZE &&
+				    CPU_ISSET(c.cpu, &aff)) {
+					cpu = c.cpu;
+					break;
+				}
+			}
+		}
+	}
+
+	perf_cpu_map__put(online);
+	return cpu >= 0 ? cpu : 0;
+}
+
+/*
+ * Try opening @attr across task/CPU target modes, counting/sampling modes,
+ * and privilege exclusion combinations until a working baseline configuration
+ * is found.
+ */
+static bool try_open_attr_variants(struct perf_event_attr *attr,
+				   bool is_uncore, int pmu_cpu,
+				   unsigned long flags,
+				   pid_t *out_pid, int *out_cpu,
+				   int *last_err)
+{
+	pid_t pids[2];
+	size_t nr_pids = 2;
+
+	if (is_uncore) {
+		pids[0] = -1;
+		pids[1] = 0;
+	} else {
+		pids[0] = 0;
+		pids[1] = -1;
+	}
+
+	for (size_t p = 0; p < nr_pids; p++) {
+		pid_t pid = pids[p];
+
+		for (int samp = 0; samp < 2; samp++) {
+			for (int priv = 0; priv < 4; priv++) {
+				struct perf_event_attr try_attr = *attr;
+				int fd;
+
+				try_attr.size = sizeof(try_attr);
+				try_attr.disabled = 1;
+				try_attr.read_format = 0;
+				if (samp == 0) {
+					try_attr.sample_period = 0;
+					try_attr.freq = 0;
+					try_attr.sample_type = 0;
+				} else {
+					try_attr.sample_period = 10000;
+					try_attr.freq = 0;
+					try_attr.sample_type = PERF_SAMPLE_IP;
+				}
+
+				if (priv >= 1)
+					try_attr.exclude_kernel = 1;
+				if (priv >= 2)
+					try_attr.exclude_hv = 1;
+				if (priv >= 3)
+					try_attr.exclude_guest = 1;
+
+				fd = sys_perf_event_open(&try_attr, pid, pmu_cpu, -1, flags);
+				if (fd >= 0) {
+					close(fd);
+					*attr = try_attr;
+					*out_pid = pid;
+					*out_cpu = pmu_cpu;
+					return true;
+				}
+				if (*last_err == 0 || *last_err == ENOENT || *last_err == EINVAL)
+					*last_err = errno;
+			}
+		}
+	}
+	return false;
+}
+
+static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
+{
+	struct probe_candidates cands = { .nr = 0 };
+	struct perf_event_attr base_attr;
+	char uprobe_exe_path[PATH_MAX];
+	__u64 uprobe_offset = 0;
+	unsigned long flags = perf_event_open_cloexec_flag();
+	int keepalive_fd = -1;
+	pid_t pid = 0;
+	int cpu = 0;
+	int last_err = 0;
+	bool found = false;
+
+	if (pmu->type == PERF_TYPE_TRACEPOINT) {
+		static const struct {
+			const char *sys;
+			const char *name;
+		} fast_tps[] = {
+			{ "sched", "sched_switch" },
+			{ "raw_syscalls", "sys_enter" },
+			{ "irq", "irq_handler_entry" },
+		};
+
+		for (size_t i = 0; i < ARRAY_SIZE(fast_tps); i++) {
+			int tp_id = tp_pmu__id(fast_tps[i].sys, fast_tps[i].name);
+
+			if (tp_id >= 0) {
+				memset(&base_attr, 0, sizeof(base_attr));
+				base_attr.type = PERF_TYPE_TRACEPOINT;
+				base_attr.config = tp_id;
+				if (try_open_attr_variants(&base_attr, false, pmu_cpu,
+							   flags, &pid, &cpu, &last_err)) {
+					char tp_buf[128];
+
+					snprintf(tp_buf, sizeof(tp_buf), "%s:%s",
+						 fast_tps[i].sys, fast_tps[i].name);
+					pmu->probed.probe_event = strdup(tp_buf);
+					found = true;
+					break;
+				}
+			}
+		}
+		if (!found) {
+			perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false,
+						 &cands, collect_probe_candidate_cb);
+		}
+	} else if (pmu->name && !strcmp(pmu->name, "kprobe")) {
+		static const char * const kprobe_funcs[] = {
+			"vfs_read",
+			"schedule",
+			"do_sys_openat2",
+		};
+
+		for (size_t i = 0; i < ARRAY_SIZE(kprobe_funcs); i++) {
+			memset(&base_attr, 0, sizeof(base_attr));
+			base_attr.type = pmu->type;
+			base_attr.size = sizeof(base_attr);
+			base_attr.kprobe_func = (uintptr_t)kprobe_funcs[i];
+			base_attr.probe_offset = 0;
+			if (try_open_attr_variants(&base_attr, false, pmu_cpu,
+						   flags, &pid, &cpu, &last_err)) {
+				char kp_buf[128];
+
+				snprintf(kp_buf, sizeof(kp_buf), "kprobe:%s",
+					 kprobe_funcs[i]);
+				pmu->probed.probe_event = strdup(kp_buf);
+				found = true;
+				break;
+			}
+		}
+	} else if (pmu->name && !strcmp(pmu->name, "uprobe")) {
+		if (get_self_uprobe_target(uprobe_exe_path, &uprobe_offset)) {
+			memset(&base_attr, 0, sizeof(base_attr));
+			base_attr.type = pmu->type;
+			base_attr.size = sizeof(base_attr);
+			base_attr.uprobe_path = (uintptr_t)uprobe_exe_path;
+			base_attr.probe_offset = uprobe_offset;
+			if (try_open_attr_variants(&base_attr, false, pmu_cpu,
+						   flags, &pid, &cpu, &last_err)) {
+				const char *slash = strrchr(uprobe_exe_path, '/');
+				const char *bname = slash ? slash + 1 : uprobe_exe_path;
+
+				if (asprintf(&pmu->probed.probe_event, "uprobe:%s+0x%llx",
+					     bname, (unsigned long long)uprobe_offset) < 0)
+					pmu->probed.probe_event = NULL;
+				found = true;
+			}
+		}
+	} else if (pmu->is_core) {
+		static const char * const core_events[] = {
+			"cycles",
+			"instructions",
+			"branches",
+			"branch-misses",
+			"cache-misses",
+			"cache-references",
+			"bus-cycles",
+			"ref-cycles",
+		};
+		char buf[256];
+
+		for (size_t i = 0; i < ARRAY_SIZE(core_events); i++) {
+			snprintf(buf, sizeof(buf), "%s/%s/", pmu->name, core_events[i]);
+			probe_candidates__add(&cands, buf);
+		}
+		perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false,
+					 &cands, collect_probe_candidate_cb);
+	} else if (pmu->type == PERF_TYPE_SOFTWARE) {
+		static const char * const sw_events[] = {
+			"software/cpu-clock/",
+			"software/task-clock/",
+			"software/page-faults/",
+			"software/context-switches/",
+			"software/cpu-migrations/",
+			"software/minor-faults/",
+			"software/major-faults/",
+			"software/dummy/",
+		};
+
+		for (size_t i = 0; i < ARRAY_SIZE(sw_events); i++)
+			probe_candidates__add(&cands, sw_events[i]);
+	} else if (pmu->type != PERF_TYPE_BREAKPOINT) {
+		char buf[256];
+
+		/*
+		 * PMUs like AMD IBS without hardware privilege filtering need
+		 * swfilt=1 to open with exclude_kernel, as needed when probing
+		 * unprivileged. They aren't selectable and have no sysfs events.
+		 */
+		if (perf_pmu__has_format(pmu, "swfilt")) {
+			snprintf(buf, sizeof(buf), "%s/swfilt=1/", pmu->name);
+			probe_candidates__add(&cands, buf);
+		}
+		if (pmu->selectable) {
+			snprintf(buf, sizeof(buf), "%s//", pmu->name);
+			probe_candidates__add(&cands, buf);
+		}
+		perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false,
+					 &cands, collect_probe_candidate_cb);
+	}
+
+	for (size_t i = 0; !found && i < cands.nr; i++) {
+		memset(&base_attr, 0, sizeof(base_attr));
+		if (!parse_candidate_attr(cands.names[i], &base_attr))
+			continue;
+
+		if (try_open_attr_variants(&base_attr, pmu->is_uncore, pmu_cpu,
+					   flags, &pid, &cpu, &last_err)) {
+			pmu->probed.probe_event = strdup(cands.names[i]);
+			found = true;
+			break;
+		}
+	}
+
+	if (!found && pmu->is_core) {
+		memset(&base_attr, 0, sizeof(base_attr));
+		base_attr.type = PERF_TYPE_HARDWARE;
+		base_attr.config = PERF_COUNT_HW_CPU_CYCLES;
+		if (perf_pmus__host_supports_extended_type())
+			base_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
+		if (try_open_attr_variants(&base_attr, false, pmu_cpu,
+					   flags, &pid, &cpu, &last_err)) {
+			pmu->probed.probe_event = strdup("cycles");
+			found = true;
+		}
+	}
+
+	if (!found && pmu->type == PERF_TYPE_BREAKPOINT) {
+		memset(&base_attr, 0, sizeof(base_attr));
+		base_attr.type = PERF_TYPE_BREAKPOINT;
+		base_attr.bp_addr = (unsigned long)&dummy_bp_var;
+		base_attr.bp_type = HW_BREAKPOINT_W;
+		base_attr.bp_len = HW_BREAKPOINT_LEN_4;
+		if (try_open_attr_variants(&base_attr, false, pmu_cpu,
+					   flags, &pid, &cpu, &last_err)) {
+			pmu->probed.probe_event = strdup("mem:<addr>:w");
+			found = true;
+		}
+	}
+
+	if (!found && cands.nr == 0) {
+		memset(&base_attr, 0, sizeof(base_attr));
+		base_attr.type = pmu->type;
+		base_attr.size = sizeof(base_attr);
+		if (pmu->perf_event_attr_init_default)
+			pmu->perf_event_attr_init_default(pmu, &base_attr);
+		if (try_open_attr_variants(&base_attr, pmu->is_uncore, pmu_cpu,
+					   flags, &pid, &cpu, &last_err)) {
+			char buf[256];
+
+			snprintf(buf, sizeof(buf), "%s//", pmu->name);
+			pmu->probed.probe_event = strdup(buf);
+			found = true;
+		}
+	}
+
+	if (!found) {
+		probe_candidates__exit(&cands);
+		pmu->probed.open_supported = false;
+		pmu->probed.open_err = last_err ?: ENOENT;
+		pmu->probed.checked = true;
+		return;
+	}
+
+	pmu->probed.open_supported = true;
+
+	/*
+	 * For tracepoint events, closing the last fd for a tracepoint triggers
+	 * tracepoint_synchronize_unregister() (an RCU grace period) in the
+	 * kernel. Hold one disabled fd open across the probe sequence so
+	 * tp_event->perf_refcount stays >= 1 and each probe open/close is
+	 * instantaneous.
+	 */
+	if (pmu->type == PERF_TYPE_TRACEPOINT) {
+		struct perf_event_attr ka_attr = base_attr;
+
+		ka_attr.disabled = 1;
+		keepalive_fd = sys_perf_event_open(&ka_attr, pid, cpu, -1, flags);
+	}
+
+	probe_candidates__exit(&cands);
+
+	/* 1. Counting vs Sampling support */
+	if (base_attr.sample_period == 0) {
+		struct perf_event_attr samp_attr = base_attr;
+		int fd;
+
+		pmu->probed.can_count = true;
+		samp_attr.sample_period = 10000;
+		samp_attr.sample_type = PERF_SAMPLE_IP;
+		fd = sys_perf_event_open(&samp_attr, pid, cpu, -1, flags);
+		if (fd >= 0) {
+			close(fd);
+			pmu->probed.can_sample = true;
+		}
+	} else {
+		pmu->probed.can_count = false;
+		pmu->probed.can_sample = true;
+	}
+
+	if (keepalive_fd >= 0)
+		close(keepalive_fd);
+	pmu->probed.checked = true;
+}
+
+struct probe_thread_args {
+	struct perf_pmu *pmu;
+	int pmu_cpu;
+};
+
+static void *perf_pmu__probe_thread(void *arg)
+{
+	struct probe_thread_args *args = arg;
+
+	if (args->pmu_cpu >= 0 && args->pmu_cpu < CPU_SETSIZE) {
+		cpu_set_t mask;
+
+		CPU_ZERO(&mask);
+		CPU_SET(args->pmu_cpu, &mask);
+		sched_setaffinity(0, sizeof(mask), &mask);
+	}
+	__perf_pmu__probe(args->pmu, args->pmu_cpu);
+	return NULL;
+}
+
+void perf_pmu__probe(struct perf_pmu *pmu)
+{
+	struct probe_thread_args args = {
+		.pmu = pmu,
+	};
+	pthread_t thread;
+
+	if (!pmu || pmu->from_env || pmu->probed.checked)
+		return;
+	if (perf_pmu__kind(pmu) != PERF_PMU_KIND_PE)
+		return;
+
+	args.pmu_cpu = select_probe_cpu(pmu);
+	if (pthread_create(&thread, NULL, perf_pmu__probe_thread, &args) == 0)
+		pthread_join(thread, NULL);
+	else
+		__perf_pmu__probe(pmu, args.pmu_cpu);
+}
+
 static bool pmu_config_equal(__u32 type, u64 a, u64 b)
 {
 	/* Ignore the extended PMU type in the upper bits of legacy encodings. */
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index b556ec51fdeb..ddcd28b3cae5 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -212,6 +212,25 @@ struct perf_pmu {
 	 * @mem_events: List of the supported mem events
 	 */
 	struct perf_mem_event *mem_events;
+
+	/**
+	 * @probed: Capabilities discovered by probing perf_event_open() on live
+	 * systems.
+	 */
+	struct {
+		/** @probed.checked: Has perf_pmu__probe() been run on this PMU? */
+		bool checked;
+		/** @probed.open_supported: Could a probe event be opened on this PMU? */
+		bool open_supported;
+		/** @probed.can_count: Supports counting (sample_period == 0). */
+		bool can_count;
+		/** @probed.can_sample: Supports interrupt-based sampling (sample_period > 0). */
+		bool can_sample;
+		/** @probed.probe_event: Name of the event used to probe the PMU. */
+		char *probe_event;
+		/** @probed.open_err: errno if open_supported is false. */
+		int open_err;
+	} probed;
 };
 
 struct perf_pmu_info {
@@ -334,6 +353,7 @@ 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.
@@ -343,9 +363,18 @@ struct perf_pmu_caps *perf_pmu__get_cap(struct perf_pmu *pmu, const char *name);
  * 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);
 
+/**
+ * perf_pmu__probe() - Actively probe live kernel/hardware capabilities for @pmu.
+ * @pmu: The PMU to probe via perf_event_open().
+ *
+ * Populates @pmu->probed with supported modes, sample formats, privilege
+ * exclusions, register masks, memory events, group limits, rdpmc, and skid.
+ * No-op if @pmu->probed.checked is already set or @pmu->from_env is true.
+ */
+void perf_pmu__probe(struct perf_pmu *pmu);
+
 void perf_pmu__warn_invalid_config(struct perf_pmu *pmu, __u64 config,
 				   const char *name, int config_num,
 				   const char *config_name);
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 29/41] perf pmu: Probe supported sample_type flags
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (27 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 28/41] perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 30/41] perf pmu: Probe precise_ip levels Ian Rogers
                   ` (11 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Extend perf_pmu__probe() with probe_sample_types() to test which enum
perf_event_sample_format flags are supported by the PMU.

First test a common bulk sample_type mask with a single
perf_event_open() call to avoid per-bit syscall overhead when all
standard sample flags are supported, and then probe any remaining
PERF_SAMPLE_* bits individually (configuring required companion fields
such as branch_sample_type, sample_regs_user, sample_regs_intr, and
sample_stack_user). Populate probed.sample_types as well as convenience
booleans for branch stack, callchain, data_src, and weight sampling.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 95 +++++++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmu.h | 13 ++++++
 2 files changed, 108 insertions(+)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 9033bada9f47..164ed926184e 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3118,6 +3118,97 @@ static bool try_open_attr_variants(struct perf_event_attr *attr,
 	return false;
 }
 
+/*
+ * Probe which enum perf_event_sample_format flags are supported by @pmu,
+ * testing a common bulk mask first before probing individual flags.
+ */
+static void probe_sample_types(struct perf_pmu *pmu,
+			       const struct perf_event_attr *base_attr,
+			       pid_t pid, int cpu, unsigned long flags)
+{
+	u64 reg_bit = BIT_ULL(perf_arch_reg_ip(EM_HOST)) ?: 1ULL;
+	u64 bulk_mask = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
+			PERF_SAMPLE_ADDR | PERF_SAMPLE_READ | PERF_SAMPLE_CALLCHAIN |
+			PERF_SAMPLE_ID | PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD |
+			PERF_SAMPLE_STREAM_ID | PERF_SAMPLE_RAW | PERF_SAMPLE_REGS_USER |
+			PERF_SAMPLE_STACK_USER | PERF_SAMPLE_WEIGHT | PERF_SAMPLE_DATA_SRC |
+			PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_TRANSACTION |
+			PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_CGROUP |
+			PERF_SAMPLE_DATA_PAGE_SIZE | PERF_SAMPLE_CODE_PAGE_SIZE;
+	struct perf_event_attr b_attr = *base_attr;
+	int b_fd;
+
+	b_attr.disabled = 1;
+	b_attr.sample_period = base_attr->sample_period ?: 10000;
+	b_attr.sample_type = bulk_mask;
+	b_attr.sample_regs_user = reg_bit;
+	b_attr.sample_regs_intr = reg_bit;
+	b_attr.sample_stack_user = 64;
+	b_attr.exclude_callchain_kernel = b_attr.exclude_kernel;
+	b_attr.inherit = 0;
+	b_fd = sys_perf_event_open(&b_attr, pid, cpu, -1, flags);
+	if (b_fd >= 0) {
+		close(b_fd);
+		pmu->probed.sample_types |= bulk_mask;
+	}
+
+	for (unsigned int shift = 0; (1ULL << shift) < PERF_SAMPLE_MAX; shift++) {
+		u64 bit = 1ULL << shift;
+		struct perf_event_attr attr = *base_attr;
+		int fd;
+
+		if (pmu->probed.sample_types & bit)
+			continue;
+
+		attr.disabled = 1;
+		attr.sample_period = base_attr->sample_period ?: 10000;
+		attr.sample_type = bit;
+
+		switch (bit) {
+		case PERF_SAMPLE_BRANCH_STACK:
+			attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY |
+				(attr.exclude_kernel ? PERF_SAMPLE_BRANCH_USER : 0);
+			break;
+		case PERF_SAMPLE_CALLCHAIN:
+			attr.exclude_callchain_kernel = attr.exclude_kernel;
+			break;
+		case PERF_SAMPLE_REGS_USER:
+			attr.sample_regs_user = reg_bit;
+			break;
+		case PERF_SAMPLE_REGS_INTR:
+			attr.sample_regs_intr = reg_bit;
+			break;
+		case PERF_SAMPLE_STACK_USER:
+			attr.sample_stack_user = 64;
+			break;
+		case PERF_SAMPLE_READ:
+			attr.inherit = 0;
+			break;
+		default:
+			break;
+		}
+
+		fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+		if (fd < 0 && bit == PERF_SAMPLE_BRANCH_STACK) {
+			attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY;
+			fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+		}
+		if (fd < 0 && bit != PERF_SAMPLE_IP) {
+			attr.sample_type = PERF_SAMPLE_IP | bit;
+			fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+		}
+		if (fd >= 0) {
+			close(fd);
+			pmu->probed.sample_types |= bit;
+		}
+	}
+
+	pmu->probed.can_branch_stack = !!(pmu->probed.sample_types & PERF_SAMPLE_BRANCH_STACK);
+	pmu->probed.can_callchain = !!(pmu->probed.sample_types & PERF_SAMPLE_CALLCHAIN);
+	pmu->probed.can_data_src = !!(pmu->probed.sample_types & PERF_SAMPLE_DATA_SRC);
+	pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT);
+}
+
 static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 {
 	struct probe_candidates cands = { .nr = 0 };
@@ -3358,6 +3449,10 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 		pmu->probed.can_sample = true;
 	}
 
+	/* 2. All enum perf_event_sample_format types */
+	if (pmu->probed.can_sample)
+		probe_sample_types(pmu, &base_attr, pid, cpu, flags);
+
 	if (keepalive_fd >= 0)
 		close(keepalive_fd);
 	pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index ddcd28b3cae5..658485faa05d 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -226,10 +226,23 @@ struct perf_pmu {
 		bool can_count;
 		/** @probed.can_sample: Supports interrupt-based sampling (sample_period > 0). */
 		bool can_sample;
+		/** @probed.can_branch_stack: Supports PERF_SAMPLE_BRANCH_STACK. */
+		bool can_branch_stack;
+		/** @probed.can_callchain: Supports PERF_SAMPLE_CALLCHAIN. */
+		bool can_callchain;
+		/** @probed.can_data_src: Supports PERF_SAMPLE_DATA_SRC. */
+		bool can_data_src;
+		/** @probed.can_weight: Supports PERF_SAMPLE_WEIGHT. */
+		bool can_weight;
 		/** @probed.probe_event: Name of the event used to probe the PMU. */
 		char *probe_event;
 		/** @probed.open_err: errno if open_supported is false. */
 		int open_err;
+		/**
+		 * @probed.sample_types: Bitmask of supported
+		 * enum perf_event_sample_format flags.
+		 */
+		u64 sample_types;
 	} probed;
 };
 
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 30/41] perf pmu: Probe precise_ip levels
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (28 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 29/41] perf pmu: Probe supported sample_type flags Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 31/41] perf pmu: Probe privilege exclusion modifiers Ian Rogers
                   ` (10 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Extend perf_pmu__probe() to probe supported precise_ip levels (1..3) and
populate probed.precise_mask and probed.max_precise.

Some PMUs that do not implement hardware precise sampling (such as
software, breakpoint, or AUX trace PMUs) silently ignore attr.precise_ip
even on non-sampling events (sample_period == 0), whereas PMUs that
genuinely validate precise_ip either reject precise_ip > 0 on counting
events or are sampling-only PMUs. Check for this before probing levels
1..3 so non-precise PMUs are not misreported as supporting precise_ip,
while still detecting higher precision levels when core events are
forwarded to hardware sampling units (such as AMD IBS).

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 50 +++++++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmu.h |  4 ++++
 2 files changed, 54 insertions(+)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 164ed926184e..1c55265be246 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3453,6 +3453,56 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 	if (pmu->probed.can_sample)
 		probe_sample_types(pmu, &base_attr, pid, cpu, flags);
 
+	/*
+	 * 3. Precise sampling support (:p, :pp, :ppp).
+	 * PMUs that do not validate attr.precise_ip (such as software,
+	 * breakpoint, or intel_pt) silently accept precise_ip > 0 even when
+	 * sample_period == 0. A PMU that genuinely implements precise_ip
+	 * either rejects precise_ip > 0 on non-sampling events or is a
+	 * sampling-only PMU (such as AMD ibs_op).
+	 * Note: On AMD, core events like cycles:P are forwarded to IBS and
+	 * support higher precision than advertised in sysfs caps/max_precise.
+	 */
+	if (pmu->probed.can_sample && !pmu->auxtrace) {
+		bool ignores_precise_ip = false;
+
+		if (pmu->probed.can_count) {
+			struct perf_event_attr c_attr = base_attr;
+			int fd;
+
+			c_attr.sample_period = 0;
+			c_attr.freq = 0;
+			c_attr.sample_type = 0;
+			c_attr.precise_ip = 1;
+			fd = sys_perf_event_open(&c_attr, pid, cpu, -1, flags);
+			if (fd >= 0) {
+				close(fd);
+				ignores_precise_ip = true;
+			}
+		}
+
+		if (!ignores_precise_ip) {
+			for (int level = 1; level <= 3; level++) {
+				struct perf_event_attr p_attr = base_attr;
+				int fd;
+
+				p_attr.sample_period = 10000;
+				p_attr.sample_type = PERF_SAMPLE_IP;
+				p_attr.precise_ip = level;
+				fd = sys_perf_event_open(&p_attr, pid, cpu, -1, flags);
+				if (fd < 0 && p_attr.exclude_kernel && p_attr.config2 == 0) {
+					p_attr.config2 = 1;
+					fd = sys_perf_event_open(&p_attr, pid, cpu, -1, flags);
+				}
+				if (fd >= 0) {
+					close(fd);
+					pmu->probed.precise_mask |= (1u << level);
+					pmu->probed.max_precise = level;
+				}
+			}
+		}
+	}
+
 	if (keepalive_fd >= 0)
 		close(keepalive_fd);
 	pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 658485faa05d..fea65e314547 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -238,6 +238,10 @@ struct perf_pmu {
 		char *probe_event;
 		/** @probed.open_err: errno if open_supported is false. */
 		int open_err;
+		/** @probed.max_precise: Maximum precise_ip level (0..3) supported. */
+		int max_precise;
+		/** @probed.precise_mask: Bitmask of supported precise_ip levels (1..3). */
+		unsigned int precise_mask;
 		/**
 		 * @probed.sample_types: Bitmask of supported
 		 * enum perf_event_sample_format flags.
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 31/41] perf pmu: Probe privilege exclusion modifiers
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (29 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 30/41] perf pmu: Probe precise_ip levels Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 32/41] perf pmu: Probe execution and AUX area features Ian Rogers
                   ` (9 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Extend perf_pmu__probe() with test_exclude_flag() to probe whether
exclude_user, exclude_kernel, exclude_hv, exclude_idle, exclude_host,
and exclude_guest are accepted by perf_event_open() on the PMU, storing
the results in struct perf_pmu::probed.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 73 +++++++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmu.h | 12 +++++++
 2 files changed, 85 insertions(+)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 1c55265be246..a1e27e678ccf 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3118,6 +3118,65 @@ static bool try_open_attr_variants(struct perf_event_attr *attr,
 	return false;
 }
 
+enum probe_exclude_flag {
+	PROBE_EXCLUDE_USER,
+	PROBE_EXCLUDE_KERNEL,
+	PROBE_EXCLUDE_HV,
+	PROBE_EXCLUDE_IDLE,
+	PROBE_EXCLUDE_HOST,
+	PROBE_EXCLUDE_GUEST,
+};
+
+/* Test whether @which privilege exclusion modifier is accepted by perf_event_open(). */
+static bool test_exclude_flag(const struct perf_event_attr *base_attr,
+			      pid_t pid, int cpu, unsigned long flags,
+			      enum probe_exclude_flag which)
+{
+	struct perf_event_attr attr = *base_attr;
+	int fd;
+
+	switch (which) {
+	case PROBE_EXCLUDE_USER:
+		attr.exclude_user = 1;
+		break;
+	case PROBE_EXCLUDE_KERNEL:
+		attr.exclude_kernel = 1;
+		attr.exclude_user = 0;
+		break;
+	case PROBE_EXCLUDE_HV:
+		attr.exclude_hv = 1;
+		break;
+	case PROBE_EXCLUDE_IDLE:
+		attr.exclude_idle = 1;
+		break;
+	case PROBE_EXCLUDE_HOST:
+		attr.exclude_host = 1;
+		attr.exclude_guest = 0;
+		break;
+	case PROBE_EXCLUDE_GUEST:
+		attr.exclude_guest = 1;
+		attr.exclude_host = 0;
+		break;
+	default:
+		return false;
+	}
+
+	fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+	if (fd >= 0) {
+		close(fd);
+		return true;
+	}
+	if (which == PROBE_EXCLUDE_USER && attr.exclude_kernel == 1) {
+		attr.exclude_kernel = 0;
+		fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+		if (fd >= 0) {
+			close(fd);
+			return true;
+		}
+	}
+	return false;
+}
+
 /*
  * Probe which enum perf_event_sample_format flags are supported by @pmu,
  * testing a common bulk mask first before probing individual flags.
@@ -3503,6 +3562,20 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 		}
 	}
 
+	/* 4. Privilege / exclusion modifiers */
+	pmu->probed.exclude_user =
+		test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_USER);
+	pmu->probed.exclude_kernel =
+		test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_KERNEL);
+	pmu->probed.exclude_hv =
+		test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_HV);
+	pmu->probed.exclude_idle =
+		test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_IDLE);
+	pmu->probed.exclude_host =
+		test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_HOST);
+	pmu->probed.exclude_guest =
+		test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_GUEST);
+
 	if (keepalive_fd >= 0)
 		close(keepalive_fd);
 	pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index fea65e314547..a7d794d67bf2 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -234,6 +234,18 @@ struct perf_pmu {
 		bool can_data_src;
 		/** @probed.can_weight: Supports PERF_SAMPLE_WEIGHT. */
 		bool can_weight;
+		/** @probed.exclude_user: Supports perf_event_attr::exclude_user. */
+		bool exclude_user;
+		/** @probed.exclude_kernel: Supports perf_event_attr::exclude_kernel. */
+		bool exclude_kernel;
+		/** @probed.exclude_hv: Supports perf_event_attr::exclude_hv. */
+		bool exclude_hv;
+		/** @probed.exclude_idle: Supports perf_event_attr::exclude_idle. */
+		bool exclude_idle;
+		/** @probed.exclude_host: Supports perf_event_attr::exclude_host. */
+		bool exclude_host;
+		/** @probed.exclude_guest: Supports perf_event_attr::exclude_guest. */
+		bool exclude_guest;
 		/** @probed.probe_event: Name of the event used to probe the PMU. */
 		char *probe_event;
 		/** @probed.open_err: errno if open_supported is false. */
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 32/41] perf pmu: Probe execution and AUX area features
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (30 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 31/41] perf pmu: Probe privilege exclusion modifiers Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 33/41] perf pmu: Probe event group size limits Ian Rogers
                   ` (8 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Extend perf_pmu__probe() to probe event execution and grouping features
via perf_event_open() and ioctl(PERF_EVENT_IOC_ENABLE):

 - Group leader sampling (PERF_SAMPLE_READ with PERF_FORMAT_GROUP)
 - Grouping software events with the PMU's events (can_sw_grouping)
 - Child task inheritance (attr.inherit)
 - Dynamic frequency mode (attr.freq)
 - Overwrite ring buffer mode (attr.write_backward)
 - Pinned and exclusive event attributes (attr.pinned, attr.exclusive)
 - Lost-event read format (PERF_FORMAT_LOST)
 - Auto-counter reload when the PMU exposes format/acr_mask (can_acr)
 - AUX area actions on auxtrace PMUs (aux_start_paused, aux_pause,
   aux_resume, and PERF_SAMPLE_AUX snapshot sampling)

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 333 ++++++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmu.h |  32 ++++
 2 files changed, 365 insertions(+)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index a1e27e678ccf..e7e05e29b695 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -2858,6 +2858,7 @@ void perf_pmu__delete(struct perf_pmu *pmu)
 	free(pmu);
 }
 
+#define MAX_PROBE_GROUP_SIZE 64
 #define MAX_PROBE_CANDIDATES 16
 
 static unsigned long dummy_bp_var;
@@ -3268,12 +3269,52 @@ static void probe_sample_types(struct perf_pmu *pmu,
 	pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT);
 }
 
+/*
+ * Pack @value into the perf_event_attr config field named by the sysfs format
+ * @name of @pmu, e.g. "rdpmc" defined as "config1:1". Returns false if the
+ * format doesn't exist or uses an unsupported config field.
+ */
+static bool probe_format_pack(const struct perf_pmu *pmu, struct perf_event_attr *attr,
+			      const char *name, __u64 value)
+{
+	struct perf_pmu_format *format = pmu_find_format(&pmu->format, name);
+	__u64 *vp;
+
+	if (!format)
+		return false;
+
+	perf_pmu_format__load(pmu, format);
+	switch (format->value) {
+	case PERF_PMU_FORMAT_VALUE_CONFIG:
+		vp = &attr->config;
+		break;
+	case PERF_PMU_FORMAT_VALUE_CONFIG1:
+		vp = &attr->config1;
+		break;
+	case PERF_PMU_FORMAT_VALUE_CONFIG2:
+		vp = &attr->config2;
+		break;
+	case PERF_PMU_FORMAT_VALUE_CONFIG3:
+		vp = &attr->config3;
+		break;
+	case PERF_PMU_FORMAT_VALUE_CONFIG4:
+		vp = &attr->config4;
+		break;
+	default:
+		return false;
+	}
+	perf_pmu__format_pack(format->bits, value, vp, /*zero=*/true);
+	return true;
+}
+
 static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 {
 	struct probe_candidates cands = { .nr = 0 };
 	struct perf_event_attr base_attr;
+	struct perf_event_attr group_attrs[MAX_PROBE_GROUP_SIZE];
 	char uprobe_exe_path[PATH_MAX];
 	__u64 uprobe_offset = 0;
+	size_t nr_group_attrs = 0;
 	unsigned long flags = perf_event_open_cloexec_flag();
 	int keepalive_fd = -1;
 	pid_t pid = 0;
@@ -3488,6 +3529,48 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 		keepalive_fd = sys_perf_event_open(&ka_attr, pid, cpu, -1, flags);
 	}
 
+	group_attrs[nr_group_attrs++] = base_attr;
+
+	for (size_t i = 0; pmu->type != PERF_TYPE_TRACEPOINT &&
+	     i < cands.nr && nr_group_attrs < MAX_PROBE_GROUP_SIZE; i++) {
+		struct perf_event_attr cand_attr;
+		int fd;
+		bool dup = false;
+
+		memset(&cand_attr, 0, sizeof(cand_attr));
+		if (!parse_candidate_attr(cands.names[i], &cand_attr))
+			continue;
+
+		cand_attr.sample_period = base_attr.sample_period;
+		cand_attr.freq = base_attr.freq;
+		cand_attr.sample_type = base_attr.sample_type;
+		cand_attr.exclude_user = base_attr.exclude_user;
+		cand_attr.exclude_kernel = base_attr.exclude_kernel;
+		cand_attr.exclude_hv = base_attr.exclude_hv;
+		cand_attr.exclude_idle = base_attr.exclude_idle;
+		cand_attr.exclude_host = base_attr.exclude_host;
+		cand_attr.exclude_guest = base_attr.exclude_guest;
+		cand_attr.disabled = 1;
+
+		for (size_t j = 0; j < nr_group_attrs; j++) {
+			if (group_attrs[j].type == cand_attr.type &&
+			    group_attrs[j].config == cand_attr.config &&
+			    group_attrs[j].config1 == cand_attr.config1 &&
+			    group_attrs[j].config2 == cand_attr.config2) {
+				dup = true;
+				break;
+			}
+		}
+		if (dup)
+			continue;
+
+		fd = sys_perf_event_open(&cand_attr, pid, cpu, -1, flags);
+		if (fd >= 0) {
+			close(fd);
+			group_attrs[nr_group_attrs++] = cand_attr;
+		}
+	}
+
 	probe_candidates__exit(&cands);
 
 	/* 1. Counting vs Sampling support */
@@ -3576,6 +3659,256 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 	pmu->probed.exclude_guest =
 		test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_GUEST);
 
+	/* 5. Execution features */
+	if (pmu->probed.can_sample) {
+		struct perf_event_attr l_attr = base_attr;
+		struct perf_event_attr f_attr = nr_group_attrs > 1 ? group_attrs[1] : base_attr;
+		int l_fd;
+
+		l_attr.disabled = 1;
+		l_attr.sample_period = 10000;
+		l_attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_READ;
+		l_attr.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
+
+		f_attr.disabled = 0;
+		f_attr.sample_period = 0;
+		f_attr.sample_type = 0;
+		f_attr.read_format = 0;
+
+		l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags);
+		if (l_fd >= 0) {
+			int f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags);
+
+			if (f_fd >= 0) {
+				if (ioctl(l_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) >= 0)
+					pmu->probed.can_leader_sampling = true;
+				ioctl(l_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
+				close(f_fd);
+			}
+			close(l_fd);
+		}
+	}
+
+	{
+		struct perf_event_attr l_attr = base_attr;
+		int l_fd;
+
+		l_attr.disabled = 1;
+		l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags);
+		if (l_fd >= 0) {
+			struct perf_event_attr sw_attr;
+			int sw_fd;
+
+			memset(&sw_attr, 0, sizeof(sw_attr));
+			sw_attr.type = PERF_TYPE_SOFTWARE;
+			sw_attr.size = sizeof(sw_attr);
+			sw_attr.config = PERF_COUNT_SW_DUMMY;
+			sw_attr.disabled = 0;
+			sw_attr.exclude_user = base_attr.exclude_user;
+			sw_attr.exclude_kernel = base_attr.exclude_kernel;
+			sw_attr.exclude_hv = base_attr.exclude_hv;
+			sw_attr.exclude_idle = base_attr.exclude_idle;
+			sw_attr.exclude_host = base_attr.exclude_host;
+			sw_attr.exclude_guest = base_attr.exclude_guest;
+
+			sw_fd = sys_perf_event_open(&sw_attr, pid, cpu, l_fd, flags);
+			if (sw_fd < 0) {
+				sw_attr.config = PERF_COUNT_SW_CPU_CLOCK;
+				sw_fd = sys_perf_event_open(&sw_attr, pid, cpu, l_fd, flags);
+			}
+			if (sw_fd >= 0) {
+				pmu->probed.can_sw_grouping = true;
+				close(sw_fd);
+			}
+			close(l_fd);
+		}
+	}
+
+	{
+		struct perf_event_attr inh_attr = base_attr;
+		int fd;
+
+		inh_attr.disabled = 1;
+		inh_attr.inherit = 1;
+		fd = sys_perf_event_open(&inh_attr, pid, cpu, -1, flags);
+		if (fd >= 0) {
+			close(fd);
+			pmu->probed.can_inherit = true;
+		}
+	}
+
+	if (pmu->probed.can_sample) {
+		struct perf_event_attr freq_attr = base_attr;
+		int fd;
+
+		freq_attr.disabled = 1;
+		freq_attr.freq = 1;
+		freq_attr.sample_freq = 4000;
+		freq_attr.sample_type = PERF_SAMPLE_IP;
+		fd = sys_perf_event_open(&freq_attr, pid, cpu, -1, flags);
+		if (fd >= 0) {
+			close(fd);
+			pmu->probed.can_freq = true;
+		}
+	}
+
+	if (pmu->probed.can_sample) {
+		struct perf_event_attr wb_attr = base_attr;
+		int fd;
+
+		wb_attr.disabled = 1;
+		wb_attr.sample_period = 10000;
+		wb_attr.sample_type = PERF_SAMPLE_IP;
+		wb_attr.write_backward = 1;
+		fd = sys_perf_event_open(&wb_attr, pid, cpu, -1, flags);
+		if (fd >= 0) {
+			close(fd);
+			pmu->probed.can_write_backward = true;
+		}
+	}
+
+	{
+		struct perf_event_attr pin_attr = base_attr;
+		int fd;
+
+		pin_attr.disabled = 1;
+		pin_attr.pinned = 1;
+		fd = sys_perf_event_open(&pin_attr, pid, cpu, -1, flags);
+		if (fd >= 0) {
+			close(fd);
+			pmu->probed.can_pinned = true;
+		}
+	}
+
+	{
+		struct perf_event_attr exc_attr = base_attr;
+		int fd;
+
+		exc_attr.disabled = 1;
+		exc_attr.exclusive = 1;
+		fd = sys_perf_event_open(&exc_attr, pid, cpu, -1, flags);
+		if (fd >= 0) {
+			close(fd);
+			pmu->probed.can_exclusive = true;
+		}
+	}
+
+	{
+		struct perf_event_attr lost_attr = base_attr;
+		int fd;
+
+		lost_attr.disabled = 1;
+		lost_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED |
+					PERF_FORMAT_TOTAL_TIME_RUNNING |
+					PERF_FORMAT_LOST;
+		fd = sys_perf_event_open(&lost_attr, pid, cpu, -1, flags);
+		if (fd >= 0) {
+			close(fd);
+			pmu->probed.can_format_lost = true;
+		}
+	}
+
+	/* 5c. Auto-counter reload (acr_mask / ratio-to-prev) */
+	if (pmu->probed.can_sample && perf_pmu__has_format(pmu, "acr_mask")) {
+		struct perf_event_attr l_attr = base_attr;
+		struct perf_event_attr f_attr = nr_group_attrs > 1 ? group_attrs[1] : base_attr;
+		int l_fd;
+
+		l_attr.disabled = 1;
+		l_attr.freq = 0;
+		l_attr.sample_period = 200000;
+		l_attr.sample_type = PERF_SAMPLE_IP;
+
+		f_attr.disabled = 0;
+		f_attr.freq = 0;
+		f_attr.sample_period = 100000;
+		f_attr.sample_type = PERF_SAMPLE_IP;
+
+		/*
+		 * Equivalent of the group
+		 * '{cycles/acr_mask=0x2/,cycles/acr_mask=0x3/}'.
+		 */
+		if (probe_format_pack(pmu, &l_attr, "acr_mask", 0x2) &&
+		    probe_format_pack(pmu, &f_attr, "acr_mask", 0x3))
+			l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags);
+		else
+			l_fd = -1;
+		if (l_fd >= 0) {
+			int f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags);
+
+			if (f_fd >= 0) {
+				if (ioctl(l_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) >= 0)
+					pmu->probed.can_acr = true;
+				ioctl(l_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
+				close(f_fd);
+			}
+			close(l_fd);
+		}
+	}
+
+	/* 5d. AUX area event actions (start_paused, pause, resume, aux_sample) */
+	if (pmu->auxtrace) {
+		struct perf_event_attr a_attr = base_attr;
+		int l_fd;
+
+		a_attr.size = max_t(__u32, base_attr.size, PERF_ATTR_SIZE_VER8);
+		a_attr.disabled = 1;
+		a_attr.aux_start_paused = 1;
+		l_fd = sys_perf_event_open(&a_attr, pid, cpu, -1, flags);
+		if (l_fd >= 0) {
+			close(l_fd);
+			pmu->probed.can_aux_start_paused = true;
+		}
+
+		a_attr = base_attr;
+		a_attr.disabled = 1;
+		l_fd = sys_perf_event_open(&a_attr, pid, cpu, -1, flags);
+		if (l_fd >= 0) {
+			struct perf_event_attr f_attr;
+			int f_fd;
+
+			memset(&f_attr, 0, sizeof(f_attr));
+			f_attr.type = PERF_TYPE_SOFTWARE;
+			f_attr.size = max_t(__u32, base_attr.size, PERF_ATTR_SIZE_VER8);
+			f_attr.config = PERF_COUNT_SW_CPU_CLOCK;
+			f_attr.sample_period = 10000;
+			f_attr.sample_type = PERF_SAMPLE_IP;
+			f_attr.exclude_user = base_attr.exclude_user;
+			f_attr.exclude_kernel = base_attr.exclude_kernel;
+			f_attr.exclude_hv = base_attr.exclude_hv;
+			f_attr.exclude_idle = base_attr.exclude_idle;
+			f_attr.exclude_host = base_attr.exclude_host;
+			f_attr.exclude_guest = base_attr.exclude_guest;
+
+			f_attr.aux_pause = 1;
+			f_attr.aux_resume = 0;
+			f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags);
+			if (f_fd >= 0) {
+				close(f_fd);
+				pmu->probed.can_aux_pause = true;
+			}
+
+			f_attr.aux_pause = 0;
+			f_attr.aux_resume = 1;
+			f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags);
+			if (f_fd >= 0) {
+				close(f_fd);
+				pmu->probed.can_aux_resume = true;
+			}
+
+			f_attr.aux_resume = 0;
+			f_attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_AUX;
+			f_attr.aux_sample_size = 64;
+			f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags);
+			if (f_fd >= 0) {
+				close(f_fd);
+				pmu->probed.can_aux_sample = true;
+			}
+
+			close(l_fd);
+		}
+	}
+
 	if (keepalive_fd >= 0)
 		close(keepalive_fd);
 	pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index a7d794d67bf2..8efffbac3cbf 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -228,12 +228,44 @@ struct perf_pmu {
 		bool can_sample;
 		/** @probed.can_branch_stack: Supports PERF_SAMPLE_BRANCH_STACK. */
 		bool can_branch_stack;
+		/** @probed.can_leader_sampling: Supports group leader sampling (:S). */
+		bool can_leader_sampling;
+		/**
+		 * @probed.can_sw_grouping: Supports grouping software events
+		 * with this PMU's events.
+		 */
+		bool can_sw_grouping;
+		/** @probed.can_inherit: Supports perf_event_attr::inherit across child tasks. */
+		bool can_inherit;
+		/** @probed.can_freq: Supports dynamic sample frequency (perf_event_attr::freq). */
+		bool can_freq;
+		/** @probed.can_write_backward: Supports overwrite ring buffer (write_backward). */
+		bool can_write_backward;
 		/** @probed.can_callchain: Supports PERF_SAMPLE_CALLCHAIN. */
 		bool can_callchain;
 		/** @probed.can_data_src: Supports PERF_SAMPLE_DATA_SRC. */
 		bool can_data_src;
 		/** @probed.can_weight: Supports PERF_SAMPLE_WEIGHT. */
 		bool can_weight;
+		/** @probed.can_pinned: Supports perf_event_attr::pinned. */
+		bool can_pinned;
+		/** @probed.can_exclusive: Supports perf_event_attr::exclusive. */
+		bool can_exclusive;
+		/** @probed.can_format_lost: Supports PERF_FORMAT_LOST. */
+		bool can_format_lost;
+		/** @probed.can_acr: Supports auto-counter reload (acr_mask / ratio-to-prev). */
+		bool can_acr;
+		/**
+		 * @probed.can_aux_start_paused: Supports aux_start_paused
+		 * (PERF_PMU_CAP_AUX_PAUSE).
+		 */
+		bool can_aux_start_paused;
+		/** @probed.can_aux_pause: Supports aux_pause action on group followers. */
+		bool can_aux_pause;
+		/** @probed.can_aux_resume: Supports aux_resume action on group followers. */
+		bool can_aux_resume;
+		/** @probed.can_aux_sample: Supports AUX area snapshot sampling. */
+		bool can_aux_sample;
 		/** @probed.exclude_user: Supports perf_event_attr::exclude_user. */
 		bool exclude_user;
 		/** @probed.exclude_kernel: Supports perf_event_attr::exclude_kernel. */
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 33/41] perf pmu: Probe event group size limits
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (31 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 32/41] perf pmu: Probe execution and AUX area features Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 34/41] perf pmu: Probe user and interrupt sample register masks Ian Rogers
                   ` (7 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Extend perf_pmu__probe() with check_group_active() and
probe_group_limits() to determine the maximum number of events in a
group that are counted simultaneously, without multiplexing
(max_group_size). After each event is added to the group, the group is
enabled and checked to be counting by comparing time_running with
time_enabled.

The number of events in a group accepted by perf_event_open() isn't
reported as it doesn't reflect what can be counted: disabled events are
accepted so that groups may be scheduled by hand and many PMUs accept
groups of enabled events that can never be scheduled.

On core PMUs, where cycles and instructions may use dedicated fixed
counters, also probe a group of general-purpose (GP) hardware events to
determine max_gp_group_size.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 210 ++++++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmu.h |  14 +++
 2 files changed, 224 insertions(+)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index e7e05e29b695..3585147c5242 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3119,6 +3119,123 @@ static bool try_open_attr_variants(struct perf_event_attr *attr,
 	return false;
 }
 
+/*
+ * Enable @leader_fd's group briefly and verify that both the group leader and
+ * @last_fd run for 100% of their enabled time without multiplexing.
+ */
+static bool check_group_active(int leader_fd, int last_fd)
+{
+	u64 before[3] = { 0, 0, 0 };
+	u64 after[3] = { 0, 0, 0 };
+	u64 f_before[3] = { 0, 0, 0 };
+	u64 f_after[3] = { 0, 0, 0 };
+	u64 delta_ena, delta_run;
+	bool check_follower = false;
+
+	if (read(leader_fd, before, sizeof(before)) != sizeof(before))
+		return false;
+
+	if (last_fd >= 0 && read(last_fd, f_before, sizeof(f_before)) == sizeof(f_before))
+		check_follower = true;
+
+	if (ioctl(leader_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) < 0)
+		return false;
+
+	for (int spin = 0; spin < 20000; spin++)
+		barrier();
+
+	ioctl(leader_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
+
+	if (read(leader_fd, after, sizeof(after)) != sizeof(after))
+		return false;
+
+	if (check_follower && read(last_fd, f_after, sizeof(f_after)) != sizeof(f_after))
+		return false;
+
+	delta_ena = after[1] - before[1];
+	delta_run = after[2] - before[2];
+	if (delta_ena == 0 || delta_run != delta_ena)
+		return false;
+
+	if (check_follower) {
+		u64 f_delta_ena = f_after[1] - f_before[1];
+		u64 f_delta_run = f_after[2] - f_before[2];
+
+		if (f_delta_ena == 0 || f_delta_run != f_delta_ena)
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * Probe the maximum number of events in a group that are counted
+ * simultaneously, without multiplexing. Opening a group doesn't imply it can
+ * be scheduled, many PMUs accept groups of enabled events that can never be
+ * counted, so after each follower is added the group is enabled and checked
+ * to be running.
+ */
+static void probe_group_limits(const struct perf_event_attr *attrs, size_t nr_attrs,
+			       pid_t pid, int cpu, unsigned long flags,
+			       int *max_size, bool *capped)
+{
+	struct perf_event_attr leader_attr;
+	int leader_fd;
+	int fds[MAX_PROBE_GROUP_SIZE];
+	int nr_followers = 0;
+	enum rlimit_action set_rlimit = NO_CHANGE;
+
+	*max_size = 0;
+	*capped = false;
+
+	if (!attrs || nr_attrs == 0)
+		return;
+
+	leader_attr = attrs[0];
+	leader_attr.disabled = 1;
+	leader_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
+
+	/* The times are needed to determine whether the group is counting. */
+	leader_fd = sys_perf_event_open(&leader_attr, pid, cpu, -1, flags);
+	if (leader_fd < 0)
+		return;
+
+	if (!check_group_active(leader_fd, -1))
+		goto out;
+	*max_size = 1;
+
+	for (int count = 2; count <= MAX_PROBE_GROUP_SIZE; count++) {
+		struct perf_event_attr follower_attr = attrs[(count - 1) % nr_attrs];
+		int fd;
+
+		follower_attr.disabled = 0;
+		follower_attr.read_format = leader_attr.read_format;
+
+		fd = sys_perf_event_open(&follower_attr, pid, cpu, leader_fd, flags);
+		if (fd < 0 && errno == EMFILE && rlimit__increase_nofile(&set_rlimit))
+			fd = sys_perf_event_open(&follower_attr, pid, cpu, leader_fd, flags);
+		if (fd < 0) {
+			/* The leader's times show whether the whole group is counting. */
+			follower_attr.read_format = 0;
+			fd = sys_perf_event_open(&follower_attr, pid, cpu, leader_fd, flags);
+		}
+		if (fd < 0)
+			break;
+
+		fds[nr_followers++] = fd;
+		if (!check_group_active(leader_fd, fd))
+			break;
+		*max_size = count;
+	}
+
+	if (*max_size >= MAX_PROBE_GROUP_SIZE)
+		*capped = true;
+out:
+	for (int i = 0; i < nr_followers; i++)
+		close(fds[i]);
+	close(leader_fd);
+}
+
 enum probe_exclude_flag {
 	PROBE_EXCLUDE_USER,
 	PROBE_EXCLUDE_KERNEL,
@@ -3909,6 +4026,99 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 		}
 	}
 
+	/* 6. Max number of events in a group counted simultaneously. */
+	if (pmu->name && (!strcmp(pmu->name, "kprobe") || !strcmp(pmu->name, "uprobe"))) {
+		if (pmu->probed.can_leader_sampling || pmu->probed.can_sw_grouping) {
+			pmu->probed.max_group_size = MAX_PROBE_GROUP_SIZE;
+			pmu->probed.max_group_capped = true;
+		} else {
+			pmu->probed.max_group_size = 1;
+		}
+	} else {
+		probe_group_limits(group_attrs, nr_group_attrs, pid, cpu, flags,
+				   &pmu->probed.max_group_size,
+				   &pmu->probed.max_group_capped);
+	}
+
+	/*
+	 * On core PMUs, cycles/instructions may use fixed counters in addition
+	 * to general-purpose (GP) programmable counters. Probe a GP-only event
+	 * group rotating through distinct GP events to determine the GP limit.
+	 */
+	if (pmu->is_core) {
+		static const char * const gp_events[] = {
+			"branches",
+			"branch-misses",
+			"cache-misses",
+			"cache-references",
+			"bus-cycles",
+			"stalled-cycles-frontend",
+			"stalled-cycles-backend",
+		};
+		static const int gp_configs[] = {
+			PERF_COUNT_HW_BRANCH_INSTRUCTIONS,
+			PERF_COUNT_HW_BRANCH_MISSES,
+			PERF_COUNT_HW_CACHE_MISSES,
+			PERF_COUNT_HW_CACHE_REFERENCES,
+			PERF_COUNT_HW_BUS_CYCLES,
+			PERF_COUNT_HW_STALLED_CYCLES_FRONTEND,
+			PERF_COUNT_HW_STALLED_CYCLES_BACKEND,
+		};
+		struct perf_event_attr gp_attrs[MAX_PROBE_GROUP_SIZE];
+		size_t nr_gp_attrs = 0;
+		pid_t gp_pid = pid;
+		int gp_cpu = cpu;
+		int gp_err = 0;
+
+		for (size_t i = 0;
+		     i < ARRAY_SIZE(gp_events) && nr_gp_attrs < MAX_PROBE_GROUP_SIZE; i++) {
+			char gp_buf[256];
+			struct perf_event_attr gp_attr;
+			bool parsed;
+
+			snprintf(gp_buf, sizeof(gp_buf), "%s/%s/", pmu->name, gp_events[i]);
+			memset(&gp_attr, 0, sizeof(gp_attr));
+			parsed = parse_candidate_attr(gp_buf, &gp_attr);
+			if (!parsed) {
+				gp_attr.type = PERF_TYPE_HARDWARE;
+				gp_attr.size = sizeof(gp_attr);
+				gp_attr.config = gp_configs[i];
+				if (perf_pmus__host_supports_extended_type())
+					gp_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
+			}
+			gp_attr.sample_period = base_attr.sample_period;
+			gp_attr.freq = base_attr.freq;
+			gp_attr.sample_type = base_attr.sample_type;
+			gp_attr.exclude_user = base_attr.exclude_user;
+			gp_attr.exclude_kernel = base_attr.exclude_kernel;
+			gp_attr.exclude_hv = base_attr.exclude_hv;
+			gp_attr.exclude_idle = base_attr.exclude_idle;
+			gp_attr.exclude_host = base_attr.exclude_host;
+			gp_attr.exclude_guest = base_attr.exclude_guest;
+			gp_attr.disabled = 1;
+
+			if (nr_gp_attrs == 0) {
+				if (try_open_attr_variants(&gp_attr, false, pmu_cpu,
+							   flags, &gp_pid, &gp_cpu, &gp_err)) {
+					gp_attrs[nr_gp_attrs++] = gp_attr;
+				}
+			} else {
+				int fd = sys_perf_event_open(&gp_attr, gp_pid, gp_cpu, -1, flags);
+
+				if (fd >= 0) {
+					close(fd);
+					gp_attrs[nr_gp_attrs++] = gp_attr;
+				}
+			}
+		}
+
+		if (nr_gp_attrs > 0) {
+			probe_group_limits(gp_attrs, nr_gp_attrs, gp_pid, gp_cpu, flags,
+					   &pmu->probed.max_gp_group_size,
+					   &pmu->probed.max_gp_group_capped);
+		}
+	}
+
 	if (keepalive_fd >= 0)
 		close(keepalive_fd);
 	pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 8efffbac3cbf..cacb8bc5198b 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -278,6 +278,10 @@ struct perf_pmu {
 		bool exclude_host;
 		/** @probed.exclude_guest: Supports perf_event_attr::exclude_guest. */
 		bool exclude_guest;
+		/** @probed.max_group_capped: Was max_group_size capped at probe limit? */
+		bool max_group_capped;
+		/** @probed.max_gp_group_capped: Was max_gp_group_size capped at probe limit? */
+		bool max_gp_group_capped;
 		/** @probed.probe_event: Name of the event used to probe the PMU. */
 		char *probe_event;
 		/** @probed.open_err: errno if open_supported is false. */
@@ -286,6 +290,16 @@ struct perf_pmu {
 		int max_precise;
 		/** @probed.precise_mask: Bitmask of supported precise_ip levels (1..3). */
 		unsigned int precise_mask;
+		/**
+		 * @probed.max_group_size: Max events in a group counted
+		 * simultaneously, without multiplexing.
+		 */
+		int max_group_size;
+		/**
+		 * @probed.max_gp_group_size: Max general-purpose counter events
+		 * in a group counted simultaneously, without multiplexing.
+		 */
+		int max_gp_group_size;
 		/**
 		 * @probed.sample_types: Bitmask of supported
 		 * enum perf_event_sample_format flags.
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 34/41] perf pmu: Probe user and interrupt sample register masks
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (32 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 33/41] perf pmu: Probe event group size limits Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 35/41] perf pmu: Probe interrupt registers that require precise sampling Ian Rogers
                   ` (6 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Extend perf_pmu__probe() with probe_pmu_regs_one() to probe supported
register sampling masks for PERF_SAMPLE_REGS_USER and
PERF_SAMPLE_REGS_INTR when supported by the PMU.

Test the architecture's base general-purpose register mask as a batch
before falling back to per-register probing if needed, and on hardware
PMUs also probe extended GPRs (such as x86 APX R16-R31 and CET SSP) and
SIMD vector/predicate register classes via PERF_SAMPLE_REGS_ABI_SIMD.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 249 ++++++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmu.h |  28 +++++
 2 files changed, 277 insertions(+)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 3585147c5242..9179231aa7d5 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3386,6 +3386,237 @@ static void probe_sample_types(struct perf_pmu *pmu,
 	pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT);
 }
 
+/*
+ * Probe supported general-purpose, extended GPR, and SIMD vector/predicate
+ * register sampling masks for either PERF_SAMPLE_REGS_USER (!@intr) or
+ * PERF_SAMPLE_REGS_INTR (@intr).
+ */
+static void probe_pmu_regs_one(const struct perf_pmu *pmu,
+			       const struct perf_event_attr *base_attr,
+			       pid_t pid, int cpu, unsigned long flags,
+			       int max_precise, bool intr,
+			       u64 *out_mask, int *out_abi,
+			       u64 *out_simd_mask, u64 *out_pred_mask)
+{
+	int dummy_abi = -1;
+	u64 gpr_mask = intr ? perf_intr_reg_mask(EM_HOST, &dummy_abi) :
+			      perf_user_reg_mask(EM_HOST, &dummy_abi);
+	u64 sample_type = intr ? PERF_SAMPLE_REGS_INTR : PERF_SAMPLE_REGS_USER;
+	u64 mask = 0;
+	int abi = 0;
+	struct perf_event_attr attr = *base_attr;
+	int fd;
+
+	attr.disabled = 1;
+	attr.sample_period = base_attr->sample_period ?: 10000;
+	attr.sample_type = sample_type;
+
+	if (gpr_mask) {
+		if (intr)
+			attr.sample_regs_intr = gpr_mask;
+		else
+			attr.sample_regs_user = gpr_mask;
+
+		fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+		if (fd >= 0) {
+			close(fd);
+			mask |= gpr_mask;
+		} else {
+			for (int b = 0; b < 64; b++) {
+				if (!(gpr_mask & BIT_ULL(b)))
+					continue;
+				if (intr)
+					attr.sample_regs_intr = BIT_ULL(b);
+				else
+					attr.sample_regs_user = BIT_ULL(b);
+				fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+				if (fd >= 0) {
+					close(fd);
+					mask |= BIT_ULL(b);
+				}
+			}
+		}
+	}
+
+	if (perf_pmu__is_software(pmu)) {
+		*out_mask = mask;
+		*out_abi = abi;
+		return;
+	}
+
+	/*
+	 * Probe extended GPRs outside the base GPR mask when the host ABI
+	 * supports PERF_SAMPLE_REGS_ABI_SIMD (e.g. APX R16-R31 and CET SSP).
+	 * Note: perf_pmu__probe() actively probes the running host kernel via
+	 * perf_event_open(), so EM_HOST is used intentionally here.
+	 */
+	{
+		int host_abi = 0;
+		u64 host_mask = intr ? perf_intr_reg_mask(EM_HOST, &host_abi) :
+				       perf_user_reg_mask(EM_HOST, &host_abi);
+		u64 ext_gpr_mask = host_mask & ~gpr_mask;
+
+		if ((host_abi & PERF_SAMPLE_REGS_ABI_SIMD) && ext_gpr_mask) {
+			struct perf_event_attr e_attr = *base_attr;
+			bool ext_batch_ok = false;
+
+			e_attr.size = sizeof(e_attr);
+			e_attr.disabled = 1;
+			e_attr.sample_period = base_attr->sample_period ?: 10000;
+			e_attr.sample_type = sample_type;
+			e_attr.sample_simd_regs_enabled = 1;
+			if (intr)
+				e_attr.sample_regs_intr = ext_gpr_mask;
+			else
+				e_attr.sample_regs_user = ext_gpr_mask;
+
+			fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
+			if (fd < 0 && intr && max_precise > 0) {
+				e_attr.precise_ip = 1;
+				fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
+			}
+			if (fd >= 0) {
+				close(fd);
+				mask |= ext_gpr_mask;
+				abi |= PERF_SAMPLE_REGS_ABI_SIMD;
+				ext_batch_ok = true;
+			}
+
+			if (!ext_batch_ok) {
+				for (int b = 0; b < 64; b++) {
+					if (!(ext_gpr_mask & BIT_ULL(b)))
+						continue;
+					e_attr.precise_ip = base_attr->precise_ip;
+					if (intr)
+						e_attr.sample_regs_intr = BIT_ULL(b);
+					else
+						e_attr.sample_regs_user = BIT_ULL(b);
+					fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
+					if (fd < 0 && intr && max_precise > 0) {
+						e_attr.precise_ip = 1;
+						fd = sys_perf_event_open(&e_attr, pid, cpu, -1,
+									 flags);
+					}
+					if (fd >= 0) {
+						close(fd);
+						mask |= BIT_ULL(b);
+						abi |= PERF_SAMPLE_REGS_ABI_SIMD;
+					}
+				}
+			}
+		}
+	}
+
+	for (int is_pred = 0; is_pred <= 1; is_pred++) {
+		bool pred = is_pred != 0;
+		u64 class_mask = intr ?
+			perf_intr_simd_reg_class_mask(EM_HOST, pred) :
+			perf_user_simd_reg_class_mask(EM_HOST, pred);
+		u64 supported_classes = 0;
+
+		for (int reg_c = 0; reg_c < 64; reg_c++) {
+			struct perf_event_attr s_attr = *base_attr;
+			u16 qwords = 0;
+			u64 bitmap;
+
+			if (!(class_mask & BIT_ULL(reg_c)))
+				continue;
+			bitmap = intr ?
+				perf_intr_simd_reg_class_bitmap_qwords(EM_HOST, reg_c,
+								       &qwords, pred) :
+				perf_user_simd_reg_class_bitmap_qwords(EM_HOST, reg_c,
+								       &qwords, pred);
+			if (!bitmap || !qwords)
+				continue;
+
+			s_attr.size = sizeof(s_attr);
+			s_attr.disabled = 1;
+			s_attr.sample_period = base_attr->sample_period ?: 10000;
+			s_attr.sample_type = sample_type;
+			s_attr.sample_simd_regs_enabled = 1;
+			if (pred) {
+				s_attr.sample_simd_pred_reg_qwords = qwords;
+				if (intr)
+					s_attr.sample_simd_pred_reg_intr = bitmap;
+				else
+					s_attr.sample_simd_pred_reg_user = bitmap;
+			} else {
+				s_attr.sample_simd_vec_reg_qwords = qwords;
+				if (intr)
+					s_attr.sample_simd_vec_reg_intr = bitmap;
+				else
+					s_attr.sample_simd_vec_reg_user = bitmap;
+			}
+
+			fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags);
+			if (fd < 0 && intr && max_precise > 0) {
+				s_attr.precise_ip = 1;
+				fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags);
+			}
+			if (fd >= 0) {
+				close(fd);
+				supported_classes |= BIT_ULL(reg_c);
+				abi |= PERF_SAMPLE_REGS_ABI_SIMD;
+			}
+		}
+
+		if (pred)
+			*out_pred_mask = supported_classes;
+		else
+			*out_simd_mask = supported_classes;
+	}
+
+	if (!(abi & PERF_SAMPLE_REGS_ABI_SIMD)) {
+		u64 ext_mask = GENMASK_ULL(63, 32) & ~gpr_mask;
+		struct perf_event_attr x_attr = *base_attr;
+		bool batch_ok = false;
+
+		x_attr.disabled = 1;
+		x_attr.sample_period = base_attr->sample_period ?: 10000;
+		x_attr.sample_type = sample_type;
+		if (intr && max_precise > 0)
+			x_attr.precise_ip = 1;
+
+		if (ext_mask) {
+			if (intr)
+				x_attr.sample_regs_intr = ext_mask;
+			else
+				x_attr.sample_regs_user = ext_mask;
+
+			fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags);
+			if (fd >= 0) {
+				close(fd);
+				mask |= ext_mask;
+				batch_ok = true;
+			}
+		}
+
+		if (!batch_ok) {
+			for (int b = 0; b < 64; b++) {
+				const char *rname;
+
+				if (gpr_mask & BIT_ULL(b))
+					continue;
+				rname = perf_reg_name(b, EM_HOST, EF_HOST, 0);
+				if (!rname || !strcmp(rname, "unknown"))
+					continue;
+				if (intr)
+					x_attr.sample_regs_intr = BIT_ULL(b);
+				else
+					x_attr.sample_regs_user = BIT_ULL(b);
+				fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags);
+				if (fd >= 0) {
+					close(fd);
+					mask |= BIT_ULL(b);
+				}
+			}
+		}
+	}
+
+	*out_mask = mask;
+	*out_abi = abi;
+}
+
 /*
  * Pack @value into the perf_event_attr config field named by the sysfs format
  * @name of @pmu, e.g. "rdpmc" defined as "config1:1". Returns false if the
@@ -3762,6 +3993,24 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 		}
 	}
 
+	/* 3b. User and interrupt register sampling masks */
+	if (pmu->probed.sample_types & PERF_SAMPLE_REGS_USER) {
+		probe_pmu_regs_one(pmu, &base_attr, pid, cpu, flags,
+				   pmu->probed.max_precise, /*intr=*/false,
+				   &pmu->probed.user_regs_mask,
+				   &pmu->probed.user_regs_abi,
+				   &pmu->probed.user_simd_mask,
+				   &pmu->probed.user_pred_mask);
+	}
+	if (pmu->probed.sample_types & PERF_SAMPLE_REGS_INTR) {
+		probe_pmu_regs_one(pmu, &base_attr, pid, cpu, flags,
+				   pmu->probed.max_precise, /*intr=*/true,
+				   &pmu->probed.intr_regs_mask,
+				   &pmu->probed.intr_regs_abi,
+				   &pmu->probed.intr_simd_mask,
+				   &pmu->probed.intr_pred_mask);
+	}
+
 	/* 4. Privilege / exclusion modifiers */
 	pmu->probed.exclude_user =
 		test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_USER);
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index cacb8bc5198b..66ee3841872a 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -300,11 +300,39 @@ struct perf_pmu {
 		 * in a group counted simultaneously, without multiplexing.
 		 */
 		int max_gp_group_size;
+		/** @probed.user_regs_abi: ABI flags for user_regs_mask. */
+		int user_regs_abi;
+		/** @probed.intr_regs_abi: ABI flags for intr_regs_mask. */
+		int intr_regs_abi;
 		/**
 		 * @probed.sample_types: Bitmask of supported
 		 * enum perf_event_sample_format flags.
 		 */
 		u64 sample_types;
+		/** @probed.user_regs_mask: Bitmask of supported sample_regs_user registers. */
+		u64 user_regs_mask;
+		/** @probed.intr_regs_mask: Bitmask of supported sample_regs_intr registers. */
+		u64 intr_regs_mask;
+		/**
+		 * @probed.user_simd_mask: Bitmask of supported user SIMD vector
+		 * register classes.
+		 */
+		u64 user_simd_mask;
+		/**
+		 * @probed.user_pred_mask: Bitmask of supported user SIMD predicate
+		 * register classes.
+		 */
+		u64 user_pred_mask;
+		/**
+		 * @probed.intr_simd_mask: Bitmask of supported intr SIMD vector
+		 * register classes.
+		 */
+		u64 intr_simd_mask;
+		/**
+		 * @probed.intr_pred_mask: Bitmask of supported intr SIMD predicate
+		 * register classes.
+		 */
+		u64 intr_pred_mask;
 	} probed;
 };
 
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 35/41] perf pmu: Probe interrupt registers that require precise sampling
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (33 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 34/41] perf pmu: Probe user and interrupt sample register masks Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 36/41] perf pmu: Probe memory profiling events and sample formats Ian Rogers
                   ` (5 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Some interrupt registers can only be sampled with precise sampling, for
example on x86 the XMM registers are captured in PEBS records and
x86_pmu_hw_config() rejects them in sample_regs_intr without precise_ip.
The interrupt register probe tried precise sampling as a fallback, or
for the legacy extended registers unconditionally, so the probed mask
didn't show which registers require it.

Add probe_regs_open() that opens without precise sampling first and
records when only the precise open succeeds, and record the interrupt
registers and SIMD register classes that require precise sampling in
intr_regs_precise_mask, intr_simd_precise_mask and
intr_pred_precise_mask.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 117 +++++++++++++++++++++++++++---------------
 tools/perf/util/pmu.h |  16 ++++++
 2 files changed, 92 insertions(+), 41 deletions(-)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 9179231aa7d5..294c5f02d3b8 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3386,18 +3386,50 @@ static void probe_sample_types(struct perf_pmu *pmu,
 	pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT);
 }
 
+/*
+ * Open @attr, if that fails and @try_precise then retry with precise sampling,
+ * setting @needs_precise when only the precise open succeeds. Returns whether
+ * either open succeeded.
+ */
+static bool probe_regs_open(struct perf_event_attr *attr, pid_t pid, int cpu,
+			    unsigned long flags, bool try_precise, bool *needs_precise)
+{
+	u8 precise_ip = attr->precise_ip;
+	int fd = sys_perf_event_open(attr, pid, cpu, -1, flags);
+
+	*needs_precise = false;
+	if (fd < 0 && try_precise && !precise_ip) {
+		attr->precise_ip = 1;
+		fd = sys_perf_event_open(attr, pid, cpu, -1, flags);
+		attr->precise_ip = precise_ip;
+		*needs_precise = fd >= 0;
+	}
+	if (fd < 0)
+		return false;
+	close(fd);
+	return true;
+}
+
 /*
  * Probe supported general-purpose, extended GPR, and SIMD vector/predicate
  * register sampling masks for either PERF_SAMPLE_REGS_USER (!@intr) or
- * PERF_SAMPLE_REGS_INTR (@intr).
+ * PERF_SAMPLE_REGS_INTR (@intr). For interrupt registers, the registers and
+ * register classes that can only be sampled with precise sampling are
+ * recorded in @out_precise_mask, @out_simd_precise_mask and
+ * @out_pred_precise_mask, which may be NULL for user registers.
  */
 static void probe_pmu_regs_one(const struct perf_pmu *pmu,
 			       const struct perf_event_attr *base_attr,
 			       pid_t pid, int cpu, unsigned long flags,
 			       int max_precise, bool intr,
 			       u64 *out_mask, int *out_abi,
-			       u64 *out_simd_mask, u64 *out_pred_mask)
+			       u64 *out_simd_mask, u64 *out_pred_mask,
+			       u64 *out_precise_mask, u64 *out_simd_precise_mask,
+			       u64 *out_pred_precise_mask)
 {
+	bool try_precise = intr && max_precise > 0;
+	u64 precise_mask = 0, simd_precise_mask = 0, pred_precise_mask = 0;
+	bool needs_precise;
 	int dummy_abi = -1;
 	u64 gpr_mask = intr ? perf_intr_reg_mask(EM_HOST, &dummy_abi) :
 			      perf_user_reg_mask(EM_HOST, &dummy_abi);
@@ -3470,14 +3502,11 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
 			else
 				e_attr.sample_regs_user = ext_gpr_mask;
 
-			fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
-			if (fd < 0 && intr && max_precise > 0) {
-				e_attr.precise_ip = 1;
-				fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
-			}
-			if (fd >= 0) {
-				close(fd);
+			if (probe_regs_open(&e_attr, pid, cpu, flags, try_precise,
+					    &needs_precise)) {
 				mask |= ext_gpr_mask;
+				if (needs_precise)
+					precise_mask |= ext_gpr_mask;
 				abi |= PERF_SAMPLE_REGS_ABI_SIMD;
 				ext_batch_ok = true;
 			}
@@ -3486,22 +3515,16 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
 				for (int b = 0; b < 64; b++) {
 					if (!(ext_gpr_mask & BIT_ULL(b)))
 						continue;
-					e_attr.precise_ip = base_attr->precise_ip;
 					if (intr)
 						e_attr.sample_regs_intr = BIT_ULL(b);
 					else
 						e_attr.sample_regs_user = BIT_ULL(b);
-					fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
-					if (fd < 0 && intr && max_precise > 0) {
-						e_attr.precise_ip = 1;
-						fd = sys_perf_event_open(&e_attr, pid, cpu, -1,
-									 flags);
-					}
-					if (fd >= 0) {
-						close(fd);
-						mask |= BIT_ULL(b);
-						abi |= PERF_SAMPLE_REGS_ABI_SIMD;
-					}
+					if (!probe_regs_open(&e_attr, pid, cpu, flags,
+							     try_precise, &needs_precise))
+						continue;
+					mask |= BIT_ULL(b);
+					precise_mask |= needs_precise ? BIT_ULL(b) : 0;
+					abi |= PERF_SAMPLE_REGS_ABI_SIMD;
 				}
 			}
 		}
@@ -3512,7 +3535,7 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
 		u64 class_mask = intr ?
 			perf_intr_simd_reg_class_mask(EM_HOST, pred) :
 			perf_user_simd_reg_class_mask(EM_HOST, pred);
-		u64 supported_classes = 0;
+		u64 supported_classes = 0, precise_classes = 0;
 
 		for (int reg_c = 0; reg_c < 64; reg_c++) {
 			struct perf_event_attr s_attr = *base_attr;
@@ -3548,22 +3571,22 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
 					s_attr.sample_simd_vec_reg_user = bitmap;
 			}
 
-			fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags);
-			if (fd < 0 && intr && max_precise > 0) {
-				s_attr.precise_ip = 1;
-				fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags);
-			}
-			if (fd >= 0) {
-				close(fd);
+			if (probe_regs_open(&s_attr, pid, cpu, flags, try_precise,
+					    &needs_precise)) {
 				supported_classes |= BIT_ULL(reg_c);
+				if (needs_precise)
+					precise_classes |= BIT_ULL(reg_c);
 				abi |= PERF_SAMPLE_REGS_ABI_SIMD;
 			}
 		}
 
-		if (pred)
+		if (pred) {
 			*out_pred_mask = supported_classes;
-		else
+			pred_precise_mask = precise_classes;
+		} else {
 			*out_simd_mask = supported_classes;
+			simd_precise_mask = precise_classes;
+		}
 	}
 
 	if (!(abi & PERF_SAMPLE_REGS_ABI_SIMD)) {
@@ -3574,8 +3597,6 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
 		x_attr.disabled = 1;
 		x_attr.sample_period = base_attr->sample_period ?: 10000;
 		x_attr.sample_type = sample_type;
-		if (intr && max_precise > 0)
-			x_attr.precise_ip = 1;
 
 		if (ext_mask) {
 			if (intr)
@@ -3583,10 +3604,11 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
 			else
 				x_attr.sample_regs_user = ext_mask;
 
-			fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags);
-			if (fd >= 0) {
-				close(fd);
+			if (probe_regs_open(&x_attr, pid, cpu, flags, try_precise,
+					    &needs_precise)) {
 				mask |= ext_mask;
+				if (needs_precise)
+					precise_mask |= ext_mask;
 				batch_ok = true;
 			}
 		}
@@ -3604,10 +3626,11 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
 					x_attr.sample_regs_intr = BIT_ULL(b);
 				else
 					x_attr.sample_regs_user = BIT_ULL(b);
-				fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags);
-				if (fd >= 0) {
-					close(fd);
+				if (probe_regs_open(&x_attr, pid, cpu, flags, try_precise,
+						    &needs_precise)) {
 					mask |= BIT_ULL(b);
+					if (needs_precise)
+						precise_mask |= BIT_ULL(b);
 				}
 			}
 		}
@@ -3615,6 +3638,12 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
 
 	*out_mask = mask;
 	*out_abi = abi;
+	if (out_precise_mask)
+		*out_precise_mask = precise_mask;
+	if (out_simd_precise_mask)
+		*out_simd_precise_mask = simd_precise_mask;
+	if (out_pred_precise_mask)
+		*out_pred_precise_mask = pred_precise_mask;
 }
 
 /*
@@ -4000,7 +4029,10 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 				   &pmu->probed.user_regs_mask,
 				   &pmu->probed.user_regs_abi,
 				   &pmu->probed.user_simd_mask,
-				   &pmu->probed.user_pred_mask);
+				   &pmu->probed.user_pred_mask,
+				   /*out_precise_mask=*/NULL,
+				   /*out_simd_precise_mask=*/NULL,
+				   /*out_pred_precise_mask=*/NULL);
 	}
 	if (pmu->probed.sample_types & PERF_SAMPLE_REGS_INTR) {
 		probe_pmu_regs_one(pmu, &base_attr, pid, cpu, flags,
@@ -4008,7 +4040,10 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 				   &pmu->probed.intr_regs_mask,
 				   &pmu->probed.intr_regs_abi,
 				   &pmu->probed.intr_simd_mask,
-				   &pmu->probed.intr_pred_mask);
+				   &pmu->probed.intr_pred_mask,
+				   &pmu->probed.intr_regs_precise_mask,
+				   &pmu->probed.intr_simd_precise_mask,
+				   &pmu->probed.intr_pred_precise_mask);
 	}
 
 	/* 4. Privilege / exclusion modifiers */
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 66ee3841872a..21c4b1a4b608 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -333,6 +333,22 @@ struct perf_pmu {
 		 * register classes.
 		 */
 		u64 intr_pred_mask;
+		/**
+		 * @probed.intr_regs_precise_mask: Subset of intr_regs_mask that
+		 * can only be sampled with precise sampling, such as x86 XMM
+		 * registers that are captured by PEBS.
+		 */
+		u64 intr_regs_precise_mask;
+		/**
+		 * @probed.intr_simd_precise_mask: Subset of intr_simd_mask that
+		 * can only be sampled with precise sampling.
+		 */
+		u64 intr_simd_precise_mask;
+		/**
+		 * @probed.intr_pred_precise_mask: Subset of intr_pred_mask that
+		 * can only be sampled with precise sampling.
+		 */
+		u64 intr_pred_precise_mask;
 	} probed;
 };
 
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 36/41] perf pmu: Probe memory profiling events and sample formats
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (34 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 35/41] perf pmu: Probe interrupt registers that require precise sampling Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 37/41] perf pmu: Probe user-space mmap capabilities Ian Rogers
                   ` (4 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Extend perf_pmu__probe() with try_open_mem_evlist() and
probe_mem_events() to test which perf_mem_events types (such as
ldlat-loads and ldlat-stores) can be opened on the PMU and which
additional memory sample formats (PERF_SAMPLE_WEIGHT,
PERF_SAMPLE_WEIGHT_STRUCT, PERF_SAMPLE_PHYS_ADDR,
PERF_SAMPLE_DATA_PAGE_SIZE, PERF_SAMPLE_TRANSACTION) are supported with
them.

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

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 294c5f02d3b8..bc3c821153b6 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3646,6 +3646,142 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
 		*out_pred_precise_mask = pred_precise_mask;
 }
 
+static bool try_open_mem_evlist(struct evlist *evlist,
+				const struct perf_event_attr *base_attr,
+				pid_t pid, int cpu, unsigned long flags,
+				int precise_ip, u64 sample_type)
+{
+	int fds[16];
+	size_t nr_fds = 0;
+	int leader_fd = -1;
+	struct evsel *evsel;
+	bool ok = true;
+
+	evlist__for_each_entry(evlist, evsel) {
+		struct perf_event_attr attr = evsel->core.attr;
+		int fd;
+
+		if (nr_fds >= ARRAY_SIZE(fds)) {
+			ok = false;
+			break;
+		}
+
+		attr.disabled = (leader_fd < 0) ? 1 : 0;
+		attr.sample_period = attr.sample_period ?: 10000;
+		attr.sample_type = sample_type;
+		attr.exclude_user = base_attr->exclude_user;
+		attr.exclude_kernel = base_attr->exclude_kernel;
+		attr.exclude_hv = base_attr->exclude_hv;
+		attr.exclude_idle = base_attr->exclude_idle;
+		attr.exclude_host = base_attr->exclude_host;
+		attr.exclude_guest = base_attr->exclude_guest;
+		if (evsel->precise_max || precise_ip > 0)
+			attr.precise_ip = precise_ip;
+
+		fd = sys_perf_event_open(&attr, pid, cpu, leader_fd, flags);
+		if (fd < 0) {
+			ok = false;
+			break;
+		}
+		if (leader_fd < 0)
+			leader_fd = fd;
+		fds[nr_fds++] = fd;
+	}
+
+	for (size_t i = nr_fds; i > 0; i--)
+		close(fds[i - 1]);
+
+	return ok && nr_fds > 0;
+}
+
+/*
+ * Probe which perf_mem_events types (e.g. ldlat-loads, ldlat-stores) and
+ * additional memory sample formats can be opened on @pmu.
+ */
+static void probe_mem_events(struct perf_pmu *pmu,
+			     const struct perf_event_attr *base_attr,
+			     pid_t pid, int cpu, unsigned long flags)
+{
+	static const u64 extra_mem_stypes[] = {
+		PERF_SAMPLE_WEIGHT,
+		PERF_SAMPLE_WEIGHT_STRUCT,
+		PERF_SAMPLE_PHYS_ADDR,
+		PERF_SAMPLE_DATA_PAGE_SIZE,
+		PERF_SAMPLE_TRANSACTION,
+	};
+
+	if (!pmu->mem_events)
+		return;
+
+	perf_pmu__mem_events_init_pmu(pmu);
+
+	for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
+		struct perf_mem_event *e = perf_pmu__mem_events_ptr(pmu, j);
+		const char *s;
+		char buf[256];
+		struct evlist *evlist;
+		struct parse_events_error err;
+		struct evsel *first;
+		int max_p, min_p, working_p = -1;
+		u64 base_stype = PERF_SAMPLE_IP | PERF_SAMPLE_ADDR | PERF_SAMPLE_DATA_SRC;
+		int ret;
+
+		if (!e || !e->tag || !e->supported)
+			continue;
+
+		s = perf_pmu__mem_events_name(pmu, j, buf, sizeof(buf));
+		if (!s)
+			continue;
+
+		evlist = evlist__new();
+		if (!evlist)
+			continue;
+
+		parse_events_error__init(&err);
+		ret = __parse_events(evlist, s, /*pmu_filter=*/NULL,
+				     /*cputype_filter=*/false, &err, /*fake_pmu=*/false,
+				     /*warn_if_reordered=*/false, /*fake_tp=*/false);
+		parse_events_error__exit(&err);
+		if (ret || evlist__empty(evlist)) {
+			evlist__put(evlist);
+			continue;
+		}
+
+		first = evlist__first(evlist);
+		if (first->precise_max) {
+			max_p = pmu->probed.max_precise > 0 ? pmu->probed.max_precise : 3;
+			min_p = 1;
+		} else {
+			max_p = first->core.attr.precise_ip;
+			min_p = first->core.attr.precise_ip;
+		}
+
+		for (int p = max_p; p >= min_p; p--) {
+			if (try_open_mem_evlist(evlist, base_attr, pid, cpu,
+						flags, p, base_stype)) {
+				working_p = p;
+				break;
+			}
+		}
+
+		if (working_p >= 0) {
+			pmu->probed.mem_events_mask |= (1u << j);
+			pmu->probed.mem_sample_types |= PERF_SAMPLE_ADDR | PERF_SAMPLE_DATA_SRC;
+
+			for (size_t k = 0; k < ARRAY_SIZE(extra_mem_stypes); k++) {
+				u64 st = base_stype | extra_mem_stypes[k];
+
+				if (try_open_mem_evlist(evlist, base_attr, pid, cpu,
+							flags, working_p, st)) {
+					pmu->probed.mem_sample_types |= extra_mem_stypes[k];
+				}
+			}
+		}
+
+		evlist__put(evlist);
+	}
+}
+
 /*
  * Pack @value into the perf_event_attr config field named by the sysfs format
  * @name of @pmu, e.g. "rdpmc" defined as "config1:1". Returns false if the
@@ -4046,6 +4182,9 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 				   &pmu->probed.intr_pred_precise_mask);
 	}
 
+	/* 3c. Memory event types and memory sample formats */
+	probe_mem_events(pmu, &base_attr, pid, cpu, flags);
+
 	/* 4. Privilege / exclusion modifiers */
 	pmu->probed.exclude_user =
 		test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_USER);
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 21c4b1a4b608..ee56570cac00 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -304,11 +304,18 @@ struct perf_pmu {
 		int user_regs_abi;
 		/** @probed.intr_regs_abi: ABI flags for intr_regs_mask. */
 		int intr_regs_abi;
+		/** @probed.mem_events_mask: Bitmask of memory event types verified by probing. */
+		unsigned int mem_events_mask;
 		/**
 		 * @probed.sample_types: Bitmask of supported
 		 * enum perf_event_sample_format flags.
 		 */
 		u64 sample_types;
+		/**
+		 * @probed.mem_sample_types: Bitmask of memory sample formats
+		 * verified on mem_events.
+		 */
+		u64 mem_sample_types;
 		/** @probed.user_regs_mask: Bitmask of supported sample_regs_user registers. */
 		u64 user_regs_mask;
 		/** @probed.intr_regs_mask: Bitmask of supported sample_regs_intr registers. */
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 37/41] perf pmu: Probe user-space mmap capabilities
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (35 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 36/41] perf pmu: Probe memory profiling events and sample formats Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 38/41] perf pmu: Probe branch instruction skid Ian Rogers
                   ` (3 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Extend perf_pmu__probe() with probe_mmap_caps() to inspect the
perf_event_mmap_page of an opened event on the PMU.

Record whether user-space counter reading (rdpmc on x86, mrs on arm64
with format/rdpmc=1) is available in sysfs/sysctl and enabled in
cap_user_rdpmc, verify whether executing a self-read via
perf_mmap__read_self() produces non-zero advancing counts
(rdpmc_works), and record pmc_width and cap_user_time /
cap_user_time_zero / cap_user_time_short support.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 103 ++++++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmu.h |  25 ++++++++++
 2 files changed, 128 insertions(+)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index bc3c821153b6..e26563342ad1 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3820,6 +3820,106 @@ static bool probe_format_pack(const struct perf_pmu *pmu, struct perf_event_attr
 	return true;
 }
 
+/*
+ * Probe user-space perf_event_mmap_page capabilities, including user-space
+ * counter reading (rdpmc/mrs/csrr), counter bit-width, and user time fields.
+ */
+static void probe_mmap_caps(struct perf_pmu *pmu,
+			    const struct perf_event_attr *base_attr,
+			    pid_t pid, int cpu, unsigned long flags)
+{
+	struct perf_event_attr attr = *base_attr;
+	struct perf_event_mmap_page *pc;
+	long page_sz = sysconf(_SC_PAGESIZE);
+	bool has_rdpmc_cap = false;
+	int events_fd;
+	int fd = -1;
+
+	pmu->probed.rdpmc_sysfs = -1;
+
+	if (page_sz <= 0)
+		page_sz = 4096;
+
+	events_fd = perf_pmu__event_source_devices_fd();
+	if (events_fd >= 0) {
+		int pmu_dirfd = openat(events_fd, pmu->name, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
+
+		if (pmu_dirfd >= 0) {
+			if (faccessat(pmu_dirfd, "rdpmc", F_OK, 0) == 0) {
+				int rfd;
+
+				has_rdpmc_cap = true;
+				rfd = openat(pmu_dirfd, "rdpmc", O_RDONLY | O_CLOEXEC);
+				if (rfd >= 0) {
+					char rbuf[16];
+					ssize_t n = read(rfd, rbuf, sizeof(rbuf) - 1);
+
+					if (n > 0) {
+						rbuf[n] = '\0';
+						pmu->probed.rdpmc_sysfs = atoi(rbuf);
+					}
+					close(rfd);
+				}
+			}
+			close(pmu_dirfd);
+		}
+		close(events_fd);
+	}
+
+	attr.disabled = 0;
+
+	/*
+	 * On architectures such as arm64, user-space counter reading must be
+	 * explicitly requested via format/rdpmc (e.g. config1:1).
+	 */
+	if (perf_pmu__has_format(pmu, "rdpmc")) {
+		struct perf_event_attr r_attr = attr;
+		int uval;
+
+		has_rdpmc_cap = true;
+		if (pmu->probed.rdpmc_sysfs < 0 &&
+		    sysctl__read_int("kernel/perf_user_access", &uval) == 0)
+			pmu->probed.rdpmc_sysfs = uval;
+
+		if (probe_format_pack(pmu, &r_attr, "rdpmc", 1))
+			fd = sys_perf_event_open(&r_attr, 0, -1, -1, flags);
+	}
+
+	if (fd < 0 && !pmu->is_uncore)
+		fd = sys_perf_event_open(&attr, 0, -1, -1, flags);
+	if (fd < 0)
+		fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+
+	if (fd >= 0) {
+		pc = mmap(NULL, page_sz, PROT_READ, MAP_SHARED, fd, 0);
+		if (pc != MAP_FAILED) {
+			if (has_rdpmc_cap || pc->cap_user_rdpmc) {
+				pmu->probed.rdpmc_available = true;
+				pmu->probed.pmc_width = pc->pmc_width;
+			}
+			if (pc->cap_user_rdpmc && pc->index != 0) {
+				struct perf_mmap map = { .base = pc };
+				struct perf_counts_values start = { .val = 0 };
+				struct perf_counts_values end = { .val = 0 };
+
+				pmu->probed.can_user_rdpmc = true;
+				if (perf_mmap__read_self(&map, &start) == 0) {
+					for (int spin = 0; spin < 10000; spin++)
+						barrier();
+					if (perf_mmap__read_self(&map, &end) == 0 &&
+					    end.val >= start.val && end.val > 0)
+						pmu->probed.rdpmc_works = true;
+				}
+			}
+			pmu->probed.can_user_time = !!pc->cap_user_time;
+			pmu->probed.can_user_time_zero = !!pc->cap_user_time_zero;
+			pmu->probed.can_user_time_short = !!pc->cap_user_time_short;
+			munmap(pc, page_sz);
+		}
+		close(fd);
+	}
+}
+
 static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 {
 	struct probe_candidates cands = { .nr = 0 };
@@ -4348,6 +4448,9 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 		}
 	}
 
+	/* 5b. User-space mmap capabilities (rdpmc, user_time, pmc_width) */
+	probe_mmap_caps(pmu, &base_attr, pid, cpu, flags);
+
 	/* 5c. Auto-counter reload (acr_mask / ratio-to-prev) */
 	if (pmu->probed.can_sample && perf_pmu__has_format(pmu, "acr_mask")) {
 		struct perf_event_attr l_attr = base_attr;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index ee56570cac00..bb21e910da90 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -266,6 +266,24 @@ struct perf_pmu {
 		bool can_aux_resume;
 		/** @probed.can_aux_sample: Supports AUX area snapshot sampling. */
 		bool can_aux_sample;
+		/**
+		 * @probed.rdpmc_available: PMU supports user-space counter
+		 * reading (rdpmc/mrs/csrr).
+		 */
+		bool rdpmc_available;
+		/**
+		 * @probed.can_user_rdpmc: User-space counter reading enabled
+		 * (cap_user_rdpmc & index).
+		 */
+		bool can_user_rdpmc;
+		/** @probed.rdpmc_works: User-space counter read instruction executed and worked. */
+		bool rdpmc_works;
+		/** @probed.can_user_time: Supports user-space time conversion (cap_user_time). */
+		bool can_user_time;
+		/** @probed.can_user_time_zero: Supports cap_user_time_zero in mmap page. */
+		bool can_user_time_zero;
+		/** @probed.can_user_time_short: Supports cap_user_time_short in mmap page. */
+		bool can_user_time_short;
 		/** @probed.exclude_user: Supports perf_event_attr::exclude_user. */
 		bool exclude_user;
 		/** @probed.exclude_kernel: Supports perf_event_attr::exclude_kernel. */
@@ -304,8 +322,15 @@ struct perf_pmu {
 		int user_regs_abi;
 		/** @probed.intr_regs_abi: ABI flags for intr_regs_mask. */
 		int intr_regs_abi;
+		/**
+		 * @probed.rdpmc_sysfs: Value from sysfs rdpmc or perf_user_access
+		 * (-1 if unknown).
+		 */
+		int rdpmc_sysfs;
 		/** @probed.mem_events_mask: Bitmask of memory event types verified by probing. */
 		unsigned int mem_events_mask;
+		/** @probed.pmc_width: Bit-width of hardware counter for user-space rdpmc. */
+		u16 pmc_width;
 		/**
 		 * @probed.sample_types: Bitmask of supported
 		 * enum perf_event_sample_format flags.
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 38/41] perf pmu: Probe branch instruction skid
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (36 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 37/41] perf pmu: Probe user-space mmap capabilities Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 39/41] perf sysinfo: Add sysinfo subcommand Ian Rogers
                   ` (2 subsequent siblings)
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Extend perf_pmu__probe() with run_skid_branch_loop() and
probe_pmu_skid() to empirically measure branch instruction skid across
supported precise_ip levels (0..3) on core PMUs.

For each supported precise_ip level, open a branch sampling event on the
current thread, execute a NOP-runway loop ending in a backward
conditional branch on x86_64, arm64, and riscv, and compute the minimum,
maximum, average, and exact-IP (zero-skid) sample counts from the
recorded ring buffer samples.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/pmu.c | 259 ++++++++++++++++++++++++++++++++++++++++++
 tools/perf/util/pmu.h |  15 +++
 2 files changed, 274 insertions(+)

diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index e26563342ad1..a5adbd34c5f2 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3920,6 +3920,263 @@ static void probe_mmap_caps(struct perf_pmu *pmu,
 	}
 }
 
+/*
+ * Mark noinline so the compiler does not inline the multi-kilobyte .rept nop
+ * runway into callers; GCC estimates inline asm size by counting newlines in
+ * the template string and would otherwise emit short-range conditional
+ * branches (such as ARM64 tbz/tbnz with a +-32KB range) across the inlined
+ * block.
+ */
+static noinline bool run_skid_branch_loop(unsigned long iters,
+					  uintptr_t *out_s_ip,
+					  uintptr_t *out_b_ip,
+					  uintptr_t *out_e_ip,
+					  int *out_insn_sz, int *out_nop_cnt)
+{
+#if defined(__x86_64__)
+	uintptr_t s_ip, b_ip, e_ip;
+
+	asm volatile(
+		"lea 1f(%%rip), %[s_ip]\n\t"
+		"lea 2f(%%rip), %[b_ip]\n\t"
+		"lea 3f(%%rip), %[e_ip]\n\t"
+		".balign 64\n"
+		"1:\n\t"
+		".rept 8192\n\t"
+		"nop\n\t"
+		".endr\n\t"
+		"dec %[cnt]\n\t"
+		"2:\n\t"
+		"jnz 1b\n"
+		"3:\n\t"
+		: [cnt] "+r" (iters),
+		  [s_ip] "=&r" (s_ip),
+		  [b_ip] "=&r" (b_ip),
+		  [e_ip] "=&r" (e_ip)
+		:
+		: "cc", "memory"
+	);
+	*out_s_ip = s_ip;
+	*out_b_ip = b_ip;
+	*out_e_ip = e_ip;
+	*out_insn_sz = 1;
+	*out_nop_cnt = 8192;
+	return true;
+#elif defined(__aarch64__)
+	uintptr_t s_ip, b_ip, e_ip;
+
+	asm volatile(
+		"adr %[s_ip], 1f\n\t"
+		"adr %[b_ip], 2f\n\t"
+		"adr %[e_ip], 3f\n\t"
+		".balign 64\n"
+		"1:\n\t"
+		".rept 1024\n\t"
+		"nop\n\t"
+		".endr\n\t"
+		"subs %[cnt], %[cnt], #1\n\t"
+		"2:\n\t"
+		"b.ne 1b\n"
+		"3:\n\t"
+		: [cnt] "+r" (iters),
+		  [s_ip] "=&r" (s_ip),
+		  [b_ip] "=&r" (b_ip),
+		  [e_ip] "=&r" (e_ip)
+		:
+		: "cc", "memory"
+	);
+	*out_s_ip = s_ip;
+	*out_b_ip = b_ip;
+	*out_e_ip = e_ip;
+	*out_insn_sz = 4;
+	*out_nop_cnt = 1024;
+	return true;
+#elif defined(__riscv)
+	uintptr_t s_ip, b_ip, e_ip;
+
+	asm volatile(
+		".option push\n\t"
+		".option norvc\n\t"
+		"la %[s_ip], 1f\n\t"
+		"la %[b_ip], 2f\n\t"
+		"la %[e_ip], 3f\n\t"
+		".balign 64\n"
+		"1:\n\t"
+		".rept 512\n\t"
+		"nop\n\t"
+		".endr\n\t"
+		"addi %[cnt], %[cnt], -1\n\t"
+		"2:\n\t"
+		"bnez %[cnt], 1b\n"
+		"3:\n\t"
+		".option pop\n\t"
+		: [cnt] "+r" (iters),
+		  [s_ip] "=&r" (s_ip),
+		  [b_ip] "=&r" (b_ip),
+		  [e_ip] "=&r" (e_ip)
+		:
+		: "memory"
+	);
+	*out_s_ip = s_ip;
+	*out_b_ip = b_ip;
+	*out_e_ip = e_ip;
+	*out_insn_sz = 4;
+	*out_nop_cnt = 512;
+	return true;
+#else
+	(void)iters;
+	*out_s_ip = 0;
+	*out_b_ip = 0;
+	*out_e_ip = 0;
+	*out_insn_sz = 0;
+	*out_nop_cnt = 0;
+	return false;
+#endif
+}
+
+/*
+ * Measure branch instruction skid across supported precise_ip levels (0..3)
+ * by sampling a tight NOP-runway branch loop and inspecting sample IPs.
+ */
+static void probe_pmu_skid(struct perf_pmu *pmu,
+			   const struct perf_event_attr *base_attr,
+			   unsigned long flags)
+{
+	struct perf_event_attr br_attr;
+	char br_buf[256];
+	long page_sz = sysconf(_SC_PAGESIZE);
+	size_t data_sz, mmap_sz;
+
+	if (!pmu->is_core || !pmu->probed.can_sample)
+		return;
+
+	if (page_sz <= 0)
+		page_sz = 4096;
+	data_sz = 4 * (size_t)page_sz;
+	mmap_sz = (size_t)page_sz + data_sz;
+
+	memset(&br_attr, 0, sizeof(br_attr));
+	snprintf(br_buf, sizeof(br_buf), "%s/branches/", pmu->name);
+	if (!parse_candidate_attr(br_buf, &br_attr)) {
+		br_attr.type = PERF_TYPE_HARDWARE;
+		br_attr.size = base_attr->size;
+		br_attr.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
+		if (perf_pmus__host_supports_extended_type())
+			br_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
+	}
+	br_attr.exclude_user = base_attr->exclude_user;
+	br_attr.exclude_kernel = 1;
+	br_attr.exclude_hv = 1;
+	br_attr.exclude_idle = base_attr->exclude_idle;
+	br_attr.exclude_host = base_attr->exclude_host;
+	br_attr.exclude_guest = base_attr->exclude_guest;
+
+	for (int lvl = 0; lvl <= 3; lvl++) {
+		struct perf_event_attr attr = br_attr;
+		struct perf_event_mmap_page *pc;
+		uintptr_t s_ip = 0, b_ip = 0, e_ip = 0;
+		int insn_sz = 0, nop_cnt = 0;
+		void *ring;
+		u8 *data;
+		u64 head, tail = 0;
+		int samples = 0, exact = 0;
+		int min_skid = 999999, max_skid = 0;
+		long sum_skid = 0;
+		int fd;
+
+		if (lvl > 0 && !(pmu->probed.precise_mask & (1u << lvl)))
+			continue;
+
+		attr.disabled = 1;
+		attr.sample_period = 251;
+		attr.sample_type = PERF_SAMPLE_IP;
+		attr.precise_ip = lvl;
+
+		fd = sys_perf_event_open(&attr, 0, -1, -1, flags);
+		if (fd < 0 && lvl > 0 && attr.exclude_kernel && attr.config2 == 0) {
+			attr.config2 = 1;
+			fd = sys_perf_event_open(&attr, 0, -1, -1, flags);
+		}
+		if (fd < 0)
+			continue;
+
+		ring = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+		if (ring == MAP_FAILED) {
+			close(fd);
+			continue;
+		}
+
+		ioctl(fd, PERF_EVENT_IOC_RESET, 0);
+		ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
+		if (!run_skid_branch_loop(8000, &s_ip, &b_ip, &e_ip,
+					  &insn_sz, &nop_cnt)) {
+			ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
+			munmap(ring, mmap_sz);
+			close(fd);
+			break;
+		}
+		ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
+
+		pc = ring;
+		head = READ_ONCE(pc->data_head);
+		/* Ensure data_head is read before reading ring buffer contents. */
+		rmb();
+		if (head > data_sz)
+			head = data_sz;
+		data = (u8 *)ring + page_sz;
+
+		while (tail + sizeof(struct perf_event_header) <= head) {
+			struct perf_event_header *hdr =
+				(struct perf_event_header *)(data + tail);
+
+			if (hdr->size == 0 || tail + hdr->size > head)
+				break;
+			if (hdr->type == PERF_RECORD_SAMPLE &&
+			    hdr->size >= sizeof(*hdr) + sizeof(u64)) {
+				u64 ip = *(u64 *)((u8 *)hdr + sizeof(*hdr));
+				uintptr_t uip = (uintptr_t)ip;
+
+				if (uip >= s_ip && uip <= e_ip && insn_sz > 0) {
+					int skid;
+
+					if (uip == b_ip) {
+						skid = 0;
+					} else if (uip >= s_ip && uip < b_ip) {
+						long off = (long)(uip - s_ip) / insn_sz;
+
+						skid = (off < nop_cnt) ?
+						       (int)(off + 1) : (nop_cnt + 1);
+					} else {
+						skid = 1;
+					}
+
+					samples++;
+					if (hdr->misc & PERF_RECORD_MISC_EXACT_IP)
+						exact++;
+					if (skid < min_skid)
+						min_skid = skid;
+					if (skid > max_skid)
+						max_skid = skid;
+					sum_skid += skid;
+				}
+			}
+			tail += hdr->size;
+		}
+
+		if (samples > 0) {
+			pmu->probed.skid[lvl].measured = true;
+			pmu->probed.skid[lvl].samples = samples;
+			pmu->probed.skid[lvl].exact_samples = exact;
+			pmu->probed.skid[lvl].min_skid = min_skid;
+			pmu->probed.skid[lvl].max_skid = max_skid;
+			pmu->probed.skid[lvl].avg_skid = (double)sum_skid / samples;
+		}
+
+		munmap(ring, mmap_sz);
+		close(fd);
+	}
+}
+
 static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 {
 	struct probe_candidates cands = { .nr = 0 };
@@ -4256,6 +4513,8 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
 				}
 			}
 		}
+
+		probe_pmu_skid(pmu, &base_attr, flags);
 	}
 
 	/* 3b. User and interrupt register sampling masks */
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index bb21e910da90..4bdb8daf7a5a 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -381,6 +381,21 @@ struct perf_pmu {
 		 * can only be sampled with precise sampling.
 		 */
 		u64 intr_pred_precise_mask;
+		/** @probed.skid: Measured branch instruction skid at precise_ip levels (0..3). */
+		struct {
+			/** @probed.skid.measured: Was skid measured at this precise_ip level? */
+			bool measured;
+			/** @probed.skid.samples: Number of branch samples collected. */
+			int samples;
+			/** @probed.skid.exact_samples: Samples with zero skid from target IP. */
+			int exact_samples;
+			/** @probed.skid.min_skid: Minimum instruction skid observed. */
+			int min_skid;
+			/** @probed.skid.max_skid: Maximum instruction skid observed. */
+			int max_skid;
+			/** @probed.skid.avg_skid: Average instruction skid across samples. */
+			double avg_skid;
+		} skid[4];
 	} probed;
 };
 
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 39/41] perf sysinfo: Add sysinfo subcommand
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (37 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 38/41] perf pmu: Probe branch instruction skid Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 40/41] perf docs: Add perf-sysinfo man page Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 41/41] perf test: Add perf sysinfo shell test Ian Rogers
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Add the 'perf sysinfo' subcommand to display and probe PMUs and system
performance monitoring capabilities on the running system or from a
perf.data file.

By default, 'perf sysinfo' reports a system-wide summary of core PMU
features (precise sampling, branch stack sampling, user-space counter
reading, leader sampling, memory profiling events, and hardware AUX
trace PMUs), memory and PCIe/IO interconnect bus bandwidth monitoring,
total events and metrics, pipeline metric drill-down levels, and an
overall system rating.

When '-p'/'--pmus' (or a PMU glob filter, '-v'/'--verbose',
'-a'/'--all', or '-j'/'--json') is specified, it also reports per-PMU
static attributes and live probed capabilities in text or JSON format.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/Build             |    1 +
 tools/perf/builtin-help.c    |    3 +-
 tools/perf/builtin-sysinfo.c | 2022 ++++++++++++++++++++++++++++++++++
 tools/perf/builtin.h         |    1 +
 tools/perf/perf.c            |    1 +
 5 files changed, 2027 insertions(+), 1 deletion(-)
 create mode 100644 tools/perf/builtin-sysinfo.c

diff --git a/tools/perf/Build b/tools/perf/Build
index dcba5ba55206..8a3f6071fa40 100644
--- a/tools/perf/Build
+++ b/tools/perf/Build
@@ -13,6 +13,7 @@ perf-y += builtin-list.o
 perf-y += builtin-record.o
 perf-y += builtin-report.o
 perf-y += builtin-stat.o
+perf-y += builtin-sysinfo.o
 perf-y += builtin-top.o
 perf-y += builtin-script.o
 perf-y += builtin-kvm.o
diff --git a/tools/perf/builtin-help.c b/tools/perf/builtin-help.c
index 5bbdb4d9b9c0..fbab4b77761b 100644
--- a/tools/perf/builtin-help.c
+++ b/tools/perf/builtin-help.c
@@ -329,6 +329,7 @@ void list_common_cmds_help(void)
 		{"report", "Read perf.data (created by perf record) and display the profile"},
 		{"script", "Read perf.data (created by perf record) and display trace output"},
 		{"stat", "Run a command and gather performance counter statistics"},
+		{"sysinfo", "Display and probe PMU and system performance monitoring information"},
 		{"test", "Runs sanity tests."},
 		{"top", "System profiling tool."},
 		{"version", "display the version of perf binary"},
@@ -485,7 +486,7 @@ int cmd_help(int argc, const char **argv)
 	};
 	const char * const builtin_help_subcommands[] = {
 		"buildid-cache", "buildid-list", "diff", "evlist", "help", "list",
-		"record", "report", "bench", "stat", "timechart", "top", "annotate",
+		"record", "report", "bench", "stat", "sysinfo", "timechart", "top", "annotate",
 		"script", "sched", "kallsyms", "kmem", "lock", "kvm", "test", "inject", "mem", "data",
 #ifdef HAVE_LIBELF_SUPPORT
 		"probe",
diff --git a/tools/perf/builtin-sysinfo.c b/tools/perf/builtin-sysinfo.c
new file mode 100644
index 000000000000..afc4648098dd
--- /dev/null
+++ b/tools/perf/builtin-sysinfo.c
@@ -0,0 +1,2022 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * builtin-sysinfo.c
+ *
+ * Builtin sysinfo command: Display and probe PMU and system performance
+ * monitoring information in a system or a perf.data file.
+ */
+#include <errno.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#include <dwarf-regs.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/perf_event.h>
+#include <linux/zalloc.h>
+#include <subcmd/parse-options.h>
+
+#include "builtin.h"
+
+#include "util/color.h"
+#include "util/cpumap.h"
+#include "util/data.h"
+#include "util/debug.h"
+#include "util/env.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/header.h"
+#include "util/mem-events.h"
+#include "util/metricgroup.h"
+#include "util/perf_regs.h"
+#include "util/pmu.h"
+#include "util/pmus.h"
+#include "util/session.h"
+#include "util/strbuf.h"
+#include "util/string2.h"
+#include "util/strlist.h"
+#include "util/tool.h"
+
+static const char *pmu_type_desc(u32 type)
+{
+	switch (type) {
+	case PERF_TYPE_HARDWARE:
+		return "PERF_TYPE_HARDWARE";
+	case PERF_TYPE_SOFTWARE:
+		return "PERF_TYPE_SOFTWARE";
+	case PERF_TYPE_TRACEPOINT:
+		return "PERF_TYPE_TRACEPOINT";
+	case PERF_TYPE_HW_CACHE:
+		return "PERF_TYPE_HW_CACHE";
+	case PERF_TYPE_RAW:
+		return "PERF_TYPE_RAW";
+	case PERF_TYPE_BREAKPOINT:
+		return "PERF_TYPE_BREAKPOINT";
+	default:
+		if (type <= PERF_PMU_TYPE_PE_END)
+			return "dynamic";
+		if (type <= PERF_PMU_TYPE_DRM_END)
+			return "drm";
+		if (type <= PERF_PMU_TYPE_HWMON_END)
+			return "hwmon";
+		if (type == PERF_PMU_TYPE_TOOL)
+			return "tool";
+		return "fake";
+	}
+}
+
+static const char *format_config_name(int config)
+{
+	static const char * const config_names[PERF_PMU_FORMAT_VALUE_CONFIG_END] = {
+		[PERF_PMU_FORMAT_VALUE_CONFIG]  = "config",
+		[PERF_PMU_FORMAT_VALUE_CONFIG1] = "config1",
+		[PERF_PMU_FORMAT_VALUE_CONFIG2] = "config2",
+		[PERF_PMU_FORMAT_VALUE_CONFIG3] = "config3",
+		[PERF_PMU_FORMAT_VALUE_CONFIG4] = "config4",
+	};
+
+	if (config >= 0 && config < PERF_PMU_FORMAT_VALUE_CONFIG_END)
+		return config_names[config];
+	return "config?";
+}
+
+static bool pmu_one_matches_filter(const struct perf_pmu *pmu, const char *pat)
+{
+	if (perf_pmu__wildcard_match(pmu, pat))
+		return true;
+	if (pmu->name && (strglobmatch(pmu->name, pat) || !strcmp(pmu->name, pat)))
+		return true;
+	if (pmu->alias_name &&
+	    (strglobmatch(pmu->alias_name, pat) || !strcmp(pmu->alias_name, pat)))
+		return true;
+	if (pmu->name) {
+		size_t base_len = pmu_name_len_no_suffix(pmu->name);
+
+		if (strlen(pat) == base_len && !strncmp(pmu->name, pat, base_len))
+			return true;
+	}
+	return false;
+}
+
+static bool pmu_matches_filters(const struct perf_pmu *pmu, bool show_all,
+				int argc, const char **argv)
+{
+	size_t num_dups;
+
+	if (argc <= 0)
+		return true;
+
+	num_dups = show_all ? 1 : perf_pmu__num_duplicates(pmu);
+	for (int i = 0; i < argc; i++) {
+		const char *pat = argv[i];
+
+		if (pmu_one_matches_filter(pmu, pat))
+			return true;
+		if (num_dups > 1 && pmu->name && pmu->pmus) {
+			size_t base_len = pmu_name_len_no_suffix(pmu->name);
+			struct perf_pmu *dup = (struct perf_pmu *)pmu;
+
+			while ((dup = perf_pmus__scan(pmu->pmus, dup)) != NULL) {
+				if (!dup->name ||
+				    pmu_name_len_no_suffix(dup->name) != base_len ||
+				    strncmp(pmu->name, dup->name, base_len) != 0)
+					break;
+				if (pmu_one_matches_filter(dup, pat))
+					return true;
+			}
+		}
+	}
+	return false;
+}
+
+static void json_escape_fprintf(FILE *fp, struct strbuf *buf, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	strbuf_setlen(buf, 0);
+	for (size_t fmt_pos = 0; fmt_pos < strlen(fmt); fmt_pos++) {
+		switch (fmt[fmt_pos]) {
+		case '%':
+			fmt_pos++;
+			switch (fmt[fmt_pos]) {
+			case 's': {
+				const char *s = va_arg(args, const char*);
+
+				strbuf_addstr(buf, s);
+				break;
+			}
+			case 'S': {
+				const char *s = va_arg(args, const char*);
+
+				for (size_t s_pos = 0; s[s_pos] != '\0'; s_pos++) {
+					switch (s[s_pos]) {
+					case '\n':
+						strbuf_addstr(buf, "\\n");
+						break;
+					case '\r':
+						strbuf_addstr(buf, "\\r");
+						break;
+					case '\t':
+						strbuf_addstr(buf, "\\t");
+						break;
+					case '\\':
+						fallthrough;
+					case '\"':
+						strbuf_addch(buf, '\\');
+						fallthrough;
+					default:
+						strbuf_addch(buf, s[s_pos]);
+						break;
+					}
+				}
+				break;
+			}
+			default:
+				strbuf_addch(buf, '%');
+				strbuf_addch(buf, fmt[fmt_pos]);
+				break;
+			}
+			break;
+		default:
+			strbuf_addch(buf, fmt[fmt_pos]);
+			break;
+		}
+	}
+	va_end(args);
+	fputs(buf->buf, fp);
+}
+
+struct print_format_state {
+	FILE *fp;
+	struct strbuf *buf;
+	bool json;
+	int count;
+};
+
+static int print_format_cb(void *vstate, const char *name, int config,
+			   const unsigned long *bits)
+{
+	struct print_format_state *state = vstate;
+	char bits_buf[128];
+
+	if (!bits)
+		return 0;
+
+	bitmap_scnprintf((unsigned long *)bits, PERF_PMU_FORMAT_BITS,
+			 bits_buf, sizeof(bits_buf));
+
+	if (state->json) {
+		char spec[160];
+
+		if (state->count == 0)
+			fprintf(state->fp, ",\n\t\"Formats\": {\n");
+
+		snprintf(spec, sizeof(spec), "%s:%s",
+			 format_config_name(config), bits_buf);
+		json_escape_fprintf(state->fp, state->buf, "%s\t\t\"%S\": \"%S\"",
+				    state->count > 0 ? ",\n" : "",
+				    name, spec);
+	} else {
+		if (state->count == 0)
+			fprintf(state->fp, "  Formats: ");
+
+		fprintf(state->fp, "%s%s (%s:%s)",
+			state->count > 0 ? ", " : "",
+			name, format_config_name(config), bits_buf);
+	}
+	state->count++;
+	return 0;
+}
+
+static bool evsel_matches_pmu(const struct evsel *evsel,
+			      const struct perf_pmu *pmu, bool show_all)
+{
+	struct perf_pmu *ev_pmu = evsel__find_pmu(evsel);
+	const char *ev_pmu_name;
+
+	if (ev_pmu == pmu)
+		return true;
+
+	if (ev_pmu) {
+		if (!show_all && !pmu->is_core && pmu->name && ev_pmu->name) {
+			size_t base_len = pmu_name_len_no_suffix(pmu->name);
+
+			if (pmu_name_len_no_suffix(ev_pmu->name) == base_len &&
+			    !strncmp(pmu->name, ev_pmu->name, base_len))
+				return true;
+		}
+		return false;
+	}
+
+	ev_pmu_name = evsel__pmu_name(evsel);
+	if (ev_pmu_name && pmu->name) {
+		if (!strcmp(ev_pmu_name, pmu->name))
+			return true;
+		if (!show_all && !pmu->is_core) {
+			size_t base_len = pmu_name_len_no_suffix(pmu->name);
+
+			if (pmu_name_len_no_suffix(ev_pmu_name) == base_len &&
+			    !strncmp(pmu->name, ev_pmu_name, base_len))
+				return true;
+		}
+	}
+	return false;
+}
+
+static void print_recorded_events(FILE *fp, struct strbuf *buf,
+				  const struct perf_pmu *pmu,
+				  const struct perf_session *session,
+				  bool show_all, bool json)
+{
+	struct strlist *ev_names;
+	struct str_node *pos;
+	struct evsel *evsel;
+	size_t count = 0;
+
+	if (!session || !session->evlist)
+		return;
+
+	ev_names = strlist__new(NULL, NULL);
+	if (!ev_names)
+		return;
+
+	evlist__for_each_entry(session->evlist, evsel) {
+		if (evsel_matches_pmu(evsel, pmu, show_all))
+			strlist__add(ev_names, evsel__name(evsel));
+	}
+
+	if (strlist__empty(ev_names)) {
+		strlist__delete(ev_names);
+		return;
+	}
+
+	if (json) {
+		fprintf(fp, ",\n\t\"RecordedEvents\": [");
+		strlist__for_each_entry(pos, ev_names) {
+			json_escape_fprintf(fp, buf, "%s\"%S\"",
+					    count > 0 ? ", " : "", pos->s);
+			count++;
+		}
+		fprintf(fp, "]");
+	} else {
+		fprintf(fp, "  Recorded events: ");
+		strlist__for_each_entry(pos, ev_names) {
+			fprintf(fp, "%s%s", count > 0 ? ", " : "", pos->s);
+			count++;
+		}
+		fputc('\n', fp);
+	}
+
+	strlist__delete(ev_names);
+}
+
+static const struct {
+	u64 bit;
+	const char *name;
+} sample_type_names[] = {
+	{ PERF_SAMPLE_IP,		"ip" },
+	{ PERF_SAMPLE_TID,		"tid" },
+	{ PERF_SAMPLE_TIME,		"time" },
+	{ PERF_SAMPLE_ADDR,		"addr" },
+	{ PERF_SAMPLE_READ,		"read" },
+	{ PERF_SAMPLE_CALLCHAIN,	"callchain" },
+	{ PERF_SAMPLE_ID,		"id" },
+	{ PERF_SAMPLE_CPU,		"cpu" },
+	{ PERF_SAMPLE_PERIOD,		"period" },
+	{ PERF_SAMPLE_STREAM_ID,	"stream_id" },
+	{ PERF_SAMPLE_RAW,		"raw" },
+	{ PERF_SAMPLE_BRANCH_STACK,	"branch_stack" },
+	{ PERF_SAMPLE_REGS_USER,	"regs_user" },
+	{ PERF_SAMPLE_STACK_USER,	"stack_user" },
+	{ PERF_SAMPLE_WEIGHT,		"weight" },
+	{ PERF_SAMPLE_DATA_SRC,		"data_src" },
+	{ PERF_SAMPLE_IDENTIFIER,	"identifier" },
+	{ PERF_SAMPLE_TRANSACTION,	"transaction" },
+	{ PERF_SAMPLE_REGS_INTR,	"regs_intr" },
+	{ PERF_SAMPLE_PHYS_ADDR,	"phys_addr" },
+	{ PERF_SAMPLE_AUX,		"aux" },
+	{ PERF_SAMPLE_CGROUP,		"cgroup" },
+	{ PERF_SAMPLE_DATA_PAGE_SIZE,	"data_page_size" },
+	{ PERF_SAMPLE_CODE_PAGE_SIZE,	"code_page_size" },
+	{ PERF_SAMPLE_WEIGHT_STRUCT,	"weight_struct" },
+};
+
+static const char *precise_level_desc(int lvl)
+{
+	switch (lvl) {
+	case 0:
+		return "0 - SAMPLE_IP can have arbitrary skid";
+	case 1:
+		return "1 - SAMPLE_IP must have constant skid";
+	case 2:
+		return "2 - SAMPLE_IP requested to have 0 skid";
+	case 3:
+		return "3 - SAMPLE_IP must have 0 skid, or uses randomization";
+	default:
+		return "unknown";
+	}
+}
+
+static void print_precise_levels(FILE *fp, const struct perf_pmu *pmu,
+				 unsigned int precise_mask,
+				 int max_precise, bool json)
+{
+	static const char * const pnames[] = { "", ":p", ":pp", ":ppp" };
+	bool need_sep = false;
+
+	if (json) {
+		fprintf(fp, ",\n\t\t\"PreciseLevels\": [");
+		for (int lvl = 1; lvl <= 3; lvl++) {
+			if (precise_mask & (1u << lvl)) {
+				fprintf(fp, "%s\"%s\"",
+					need_sep ? ", " : "", pnames[lvl]);
+				need_sep = true;
+			}
+		}
+		fprintf(fp, "]");
+		return;
+	}
+
+	fprintf(fp, "    Precise sampling: ");
+	if (precise_mask == 0) {
+		if (perf_pmu__is_software(pmu)) {
+			color_fprintf(fp, PERF_COLOR_GREEN, "always precise");
+			fprintf(fp, " (software event, SAMPLE_IP has 0 skid)\n");
+		} else {
+			color_fprintf(fp, PERF_COLOR_RED, "not supported");
+			fprintf(fp, " (%s)\n", precise_level_desc(0));
+		}
+		return;
+	}
+
+	for (int lvl = 1; lvl <= 3; lvl++) {
+		if (precise_mask & (1u << lvl)) {
+			if (need_sep)
+				fprintf(fp, ", ");
+			color_fprintf(fp, PERF_COLOR_GREEN, "%s", pnames[lvl]);
+			fprintf(fp, " (%s)", precise_level_desc(lvl));
+			need_sep = true;
+		}
+	}
+	fprintf(fp, " (max_precise: ");
+	color_fprintf(fp, PERF_COLOR_GREEN, "%d", max_precise);
+	fprintf(fp, ")\n");
+}
+
+static void print_measured_skid(FILE *fp, const struct perf_pmu *pmu, bool json)
+{
+	static const char * const lvl_keys[] = { "0", ":p", ":pp", ":ppp" };
+	static const char * const lvl_labels[] = {
+		"level 0 (no :p)",
+		"level 1 (:p)",
+		"level 2 (:pp)",
+		"level 3 (:ppp)",
+	};
+	const typeof(pmu->probed) *p = &pmu->probed;
+	bool any = false;
+	bool first = true;
+
+	for (int lvl = 0; lvl <= 3; lvl++) {
+		if (p->skid[lvl].measured) {
+			any = true;
+			break;
+		}
+	}
+	if (!any)
+		return;
+
+	if (json)
+		fprintf(fp, ",\n\t\t\"MeasuredSkid\": {\n");
+	else
+		fprintf(fp, "    Measured branch skid (instructions from branch):\n");
+
+	for (int lvl = 0; lvl <= 3; lvl++) {
+		if (!p->skid[lvl].measured)
+			continue;
+		if (json) {
+			fprintf(fp,
+				"%s\t\t\t\"%s\": { \"Samples\": %d, \"ExactIp\": %d, \"MinInsn\": %d, \"MaxInsn\": %d, \"AvgInsn\": %.1f }",
+				first ? "" : ",\n",
+				lvl_keys[lvl],
+				p->skid[lvl].samples,
+				p->skid[lvl].exact_samples,
+				p->skid[lvl].min_skid,
+				p->skid[lvl].max_skid,
+				p->skid[lvl].avg_skid);
+		} else {
+			const char *skid_color = PERF_COLOR_YELLOW;
+
+			if (p->skid[lvl].max_skid == 0)
+				skid_color = PERF_COLOR_GREEN;
+			else if (p->skid[lvl].max_skid <= 2)
+				skid_color = PERF_COLOR_CYAN;
+
+			fprintf(fp, "      %-15s: ", lvl_labels[lvl]);
+			color_fprintf(fp, skid_color, "min %d, max %d, avg %.1f",
+				      p->skid[lvl].min_skid,
+				      p->skid[lvl].max_skid,
+				      p->skid[lvl].avg_skid);
+			fprintf(fp, " (%d/%d exact_ip)\n",
+				p->skid[lvl].exact_samples,
+				p->skid[lvl].samples);
+		}
+		first = false;
+	}
+
+	if (json)
+		fprintf(fp, "\n\t\t}");
+}
+
+static void print_sample_type_list(FILE *fp, u64 mask, bool json)
+{
+	bool need_sep = false;
+
+	for (size_t x = 0; x < ARRAY_SIZE(sample_type_names); x++) {
+		if (!(mask & sample_type_names[x].bit))
+			continue;
+		if (json)
+			fprintf(fp, "%s\"%s\"", need_sep ? ", " : "", sample_type_names[x].name);
+		else
+			fprintf(fp, "%s%s", need_sep ? ", " : "", sample_type_names[x].name);
+		need_sep = true;
+	}
+	if (!json && !need_sep)
+		fprintf(fp, "none");
+}
+
+static void print_probed_regs(FILE *fp, struct strbuf *buf, const char *label,
+			      u64 mask, int abi, u64 simd_mask, u64 pred_mask,
+			      bool intr, bool json)
+{
+	const char *last_name = NULL;
+	bool need_sep = false;
+
+	if (!mask && !simd_mask && !pred_mask)
+		return;
+
+	if (json)
+		fprintf(fp, ",\n\t\t\"%s\": [", label);
+	else
+		fprintf(fp, "    %s: ", label);
+
+	for (int reg = 0; reg < 64; reg++) {
+		const char *name;
+
+		if (!(mask & BIT_ULL(reg)))
+			continue;
+		name = perf_reg_name(reg, EM_HOST, EF_HOST, abi);
+		if (!name || !strcmp(name, "unknown"))
+			continue;
+		if (last_name && !strcmp(last_name, name))
+			continue;
+		last_name = name;
+
+		if (json) {
+			json_escape_fprintf(fp, buf, "%s\"%S\"",
+					    need_sep ? ", " : "", name);
+		} else {
+			fprintf(fp, "%s%s", need_sep ? ", " : "", name);
+		}
+		need_sep = true;
+	}
+
+	for (int is_pred = 0; is_pred <= 1; is_pred++) {
+		bool pred = is_pred != 0;
+		u64 cmask = pred ? pred_mask : simd_mask;
+
+		for (int reg_c = 0; reg_c < 64; reg_c++) {
+			const char *cname;
+			u16 qwords = 0;
+			u64 bitmap;
+			char reg_range[64];
+
+			if (!(cmask & BIT_ULL(reg_c)))
+				continue;
+			cname = perf_simd_reg_class_name(EM_HOST, reg_c, pred);
+			bitmap = intr ?
+				perf_intr_simd_reg_class_bitmap_qwords(EM_HOST, reg_c,
+								       &qwords, pred) :
+				perf_user_simd_reg_class_bitmap_qwords(EM_HOST, reg_c,
+								       &qwords, pred);
+			if (!cname || !bitmap)
+				continue;
+			snprintf(reg_range, sizeof(reg_range), "%s0-%d",
+				 cname, fls64(bitmap) - 1);
+			if (json) {
+				json_escape_fprintf(fp, buf, "%s\"%S\"",
+						    need_sep ? ", " : "", reg_range);
+			} else {
+				fprintf(fp, "%s%s", need_sep ? ", " : "", reg_range);
+			}
+			need_sep = true;
+		}
+	}
+
+	if (json)
+		fprintf(fp, "]");
+	else
+		fputc('\n', fp);
+}
+
+static void pmu_event_counts(struct perf_pmu *pmu, size_t *out_total,
+			     size_t *out_sysfs, size_t *out_json)
+{
+	size_t total = perf_pmu__num_events(pmu);
+	size_t sysfs;
+
+	if (pmu->type == PERF_TYPE_TRACEPOINT ||
+	    (pmu->type >= PERF_PMU_TYPE_DRM_START &&
+	     pmu->type <= PERF_PMU_TYPE_DRM_END) ||
+	    (pmu->type >= PERF_PMU_TYPE_HWMON_START &&
+	     pmu->type <= PERF_PMU_TYPE_HWMON_END))
+		sysfs = total;
+	else
+		sysfs = pmu->sysfs_aliases;
+
+	*out_total = total;
+	*out_sysfs = sysfs;
+	*out_json = total > sysfs ? total - sysfs : 0;
+}
+
+static void print_mem_events_static(FILE *fp, struct strbuf *buf,
+				    const struct perf_pmu *pmu, bool json)
+{
+	bool first = true;
+
+	if (!pmu->mem_events)
+		return;
+
+	perf_pmu__mem_events_init_pmu((struct perf_pmu *)pmu);
+
+	for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
+		struct perf_mem_event *e = perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j);
+		char ev_buf[256];
+		const char *s;
+
+		if (!e || !e->tag || !e->supported)
+			continue;
+		s = perf_pmu__mem_events_name((struct perf_pmu *)pmu, j,
+					      ev_buf, sizeof(ev_buf));
+		if (!s)
+			continue;
+
+		if (json) {
+			if (first)
+				fprintf(fp, ",\n\t\"MemoryEvents\": {\n");
+			json_escape_fprintf(fp, buf, "%s\t\t\"%S\": \"%S\"",
+					    first ? "" : ",\n", e->tag, s);
+		} else {
+			if (first)
+				fprintf(fp, "  Memory events: ");
+			fprintf(fp, "%s%s (%s)", first ? "" : ", ", e->tag, s);
+		}
+		first = false;
+	}
+
+	if (!first) {
+		if (json)
+			fprintf(fp, "\n\t}");
+		else
+			fputc('\n', fp);
+	}
+}
+
+static void print_pmu_json(FILE *fp, const struct perf_pmu *pmu,
+			   const struct perf_session *session, bool show_all)
+{
+	struct strbuf buf;
+	size_t num_instances = show_all ? 1 : perf_pmu__num_duplicates(pmu);
+	char display_name[128];
+	size_t total_events, sysfs_events, json_events;
+	struct print_format_state format_state = {
+		.fp = fp,
+		.buf = &buf,
+		.json = true,
+		.count = 0,
+	};
+
+	strbuf_init(&buf, 0);
+
+	if (num_instances > 1) {
+		size_t base_len = pmu_name_len_no_suffix(pmu->name ?: "");
+
+		snprintf(display_name, sizeof(display_name), "%.*s",
+			 (int)base_len, pmu->name ?: "");
+	} else {
+		snprintf(display_name, sizeof(display_name), "%s", pmu->name ?: "");
+	}
+
+	fprintf(fp, "{\n");
+	json_escape_fprintf(fp, &buf, "\t\"Unit\": \"%S\"", display_name);
+	if (pmu->name)
+		json_escape_fprintf(fp, &buf, ",\n\t\"FullName\": \"%S\"", pmu->name);
+	fprintf(fp, ",\n\t\"Instances\": %zu", num_instances);
+	if (pmu->alias_name)
+		json_escape_fprintf(fp, &buf, ",\n\t\"Alias\": \"%S\"", pmu->alias_name);
+	if (pmu->id)
+		json_escape_fprintf(fp, &buf, ",\n\t\"Identifier\": \"%S\"", pmu->id);
+	if (pmu->type != PERF_PMU_TYPE_FAKE) {
+		fprintf(fp, ",\n\t\"Type\": %u", pmu->type);
+		json_escape_fprintf(fp, &buf, ",\n\t\"TypeDesc\": \"%S\"",
+				    pmu_type_desc(pmu->type));
+	}
+	fprintf(fp, ",\n\t\"IsCore\": %s", pmu->is_core ? "true" : "false");
+	fprintf(fp, ",\n\t\"IsUncore\": %s", pmu->is_uncore ? "true" : "false");
+	fprintf(fp, ",\n\t\"IsSoftware\": %s", perf_pmu__is_software(pmu) ? "true" : "false");
+	fprintf(fp, ",\n\t\"Auxtrace\": %s", pmu->auxtrace ? "true" : "false");
+	fprintf(fp, ",\n\t\"Selectable\": %s", pmu->selectable ? "true" : "false");
+
+	if (pmu->cpus) {
+		char cpus_buf[256];
+		int nr_cpus = perf_cpu_map__nr(pmu->cpus);
+
+		cpu_map__snprint(pmu->cpus, cpus_buf, sizeof(cpus_buf));
+		if (cpus_buf[0]) {
+			json_escape_fprintf(fp, &buf, ",\n\t\"CPUs\": \"%S\"", cpus_buf);
+			fprintf(fp, ",\n\t\"NrCPUs\": %d", nr_cpus);
+		}
+	}
+
+	perf_pmu__caps_parse((struct perf_pmu *)pmu);
+	if (!list_empty(&pmu->caps)) {
+		struct perf_pmu_caps *cap;
+		bool first = true;
+
+		fprintf(fp, ",\n\t\"Capabilities\": {\n");
+		list_for_each_entry(cap, &pmu->caps, list) {
+			json_escape_fprintf(fp, &buf, "%s\t\t\"%S\": \"%S\"",
+					    first ? "" : ",\n",
+					    cap->name, cap->value);
+			first = false;
+		}
+		fprintf(fp, "\n\t}");
+	}
+
+	perf_pmu__for_each_format((struct perf_pmu *)pmu, &format_state, print_format_cb);
+	if (format_state.count > 0)
+		fprintf(fp, "\n\t}");
+
+	pmu_event_counts((struct perf_pmu *)pmu, &total_events,
+			 &sysfs_events, &json_events);
+	if (total_events > 0) {
+		fprintf(fp, ",\n\t\"Events\": {\n");
+		fprintf(fp, "\t\t\"Total\": %zu,\n", total_events);
+		fprintf(fp, "\t\t\"Sysfs\": %zu,\n", sysfs_events);
+		fprintf(fp, "\t\t\"Json\": %zu\n", json_events);
+		fprintf(fp, "\t}");
+	}
+
+	print_mem_events_static(fp, &buf, pmu, /*json=*/true);
+	print_recorded_events(fp, &buf, pmu, session, show_all, /*json=*/true);
+
+	if (pmu->probed.checked) {
+		const typeof(pmu->probed) *p = &pmu->probed;
+
+		fprintf(fp, ",\n\t\"Probed\": {\n");
+		fprintf(fp, "\t\t\"OpenSupported\": %s",
+			p->open_supported ? "true" : "false");
+		if (p->open_supported) {
+			bool need_sep = false;
+			const struct {
+				bool supported;
+				const char *name;
+			} excl[] = {
+				{ p->exclude_user,   "exclude_user" },
+				{ p->exclude_kernel, "exclude_kernel" },
+				{ p->exclude_hv,     "exclude_hv" },
+				{ p->exclude_idle,   "exclude_idle" },
+				{ p->exclude_host,   "exclude_host" },
+				{ p->exclude_guest,  "exclude_guest" },
+			};
+
+			if (p->probe_event) {
+				json_escape_fprintf(fp, &buf,
+						    ",\n\t\t\"ProbeEvent\": \"%S\"",
+						    p->probe_event);
+			}
+			fprintf(fp, ",\n\t\t\"Counting\": %s",
+				p->can_count ? "true" : "false");
+			fprintf(fp, ",\n\t\t\"Sampling\": %s",
+				p->can_sample ? "true" : "false");
+			fprintf(fp, ",\n\t\t\"BranchStack\": %s",
+				p->can_branch_stack ? "true" : "false");
+			if (p->can_sample) {
+				if (!pmu->auxtrace) {
+					const char *pdesc = (p->max_precise == 0 &&
+							     perf_pmu__is_software(pmu)) ?
+						"software event - SAMPLE_IP has 0 skid" :
+						precise_level_desc(p->max_precise);
+
+					fprintf(fp, ",\n\t\t\"MaxPrecise\": %d", p->max_precise);
+					json_escape_fprintf(fp, &buf,
+							    ",\n\t\t\"MaxPreciseDesc\": \"%S\"",
+							    pdesc);
+					print_precise_levels(fp, pmu, p->precise_mask,
+							     p->max_precise, /*json=*/true);
+
+					print_measured_skid(fp, pmu, /*json=*/true);
+				}
+
+				fprintf(fp, ",\n\t\t\"SampleTypes\": [");
+				print_sample_type_list(fp, p->sample_types, /*json=*/true);
+				fprintf(fp, "]");
+			}
+
+			print_probed_regs(fp, &buf, "UserRegisters",
+					  p->user_regs_mask, p->user_regs_abi,
+					  p->user_simd_mask, p->user_pred_mask,
+					  /*intr=*/false, /*json=*/true);
+			print_probed_regs(fp, &buf, "IntrRegisters",
+					  p->intr_regs_mask, p->intr_regs_abi,
+					  p->intr_simd_mask, p->intr_pred_mask,
+					  /*intr=*/true, /*json=*/true);
+			print_probed_regs(fp, &buf, "IntrRegistersPreciseOnly",
+					  p->intr_regs_precise_mask, p->intr_regs_abi,
+					  p->intr_simd_precise_mask, p->intr_pred_precise_mask,
+					  /*intr=*/true, /*json=*/true);
+
+			if (p->mem_events_mask) {
+				need_sep = false;
+				fprintf(fp, ",\n\t\t\"MemoryEvents\": [");
+				for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
+					struct perf_mem_event *e =
+						perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j);
+
+					if (e && e->tag && (p->mem_events_mask & (1u << j))) {
+						json_escape_fprintf(fp, &buf, "%s\"%S\"",
+								    need_sep ? ", " : "",
+								    e->tag);
+						need_sep = true;
+					}
+				}
+				fprintf(fp, "]");
+
+				fprintf(fp, ",\n\t\t\"MemorySampleTypes\": [");
+				print_sample_type_list(fp, p->mem_sample_types, /*json=*/true);
+				fprintf(fp, "]");
+			}
+
+			need_sep = false;
+			fprintf(fp, ",\n\t\t\"ExcludeModifiers\": [");
+			for (size_t x = 0; x < ARRAY_SIZE(excl); x++) {
+				if (excl[x].supported) {
+					fprintf(fp, "%s\"%s\"",
+						need_sep ? ", " : "",
+						excl[x].name);
+					need_sep = true;
+				}
+			}
+			fprintf(fp, "]");
+
+			{
+				const struct {
+					bool supported;
+					const char *name;
+				} feats[] = {
+					{ p->can_leader_sampling,  "leader_sampling" },
+					{ p->can_sw_grouping,      "sw_grouping" },
+					{ p->can_inherit,          "inherit" },
+					{ p->can_freq,             "sample_freq" },
+					{ p->can_write_backward,   "write_backward" },
+					{ p->can_callchain,        "callchain" },
+					{ p->can_data_src,         "data_src" },
+					{ p->can_weight,           "weight" },
+					{ p->can_pinned,           "pinned" },
+					{ p->can_exclusive,        "exclusive" },
+					{ p->can_format_lost,      "format_lost" },
+					{ p->can_acr,              "auto_counter_reload" },
+					{ p->can_aux_start_paused, "aux_start_paused" },
+					{ p->can_aux_pause,        "aux_pause" },
+					{ p->can_aux_resume,       "aux_resume" },
+					{ p->can_aux_sample,       "aux_sample" },
+					{ p->can_user_rdpmc,       "user_rdpmc" },
+					{ p->can_user_time,        "user_time" },
+					{ p->can_user_time_zero,   "user_time_zero" },
+					{ p->can_user_time_short,  "user_time_short" },
+				};
+
+				need_sep = false;
+				fprintf(fp, ",\n\t\t\"Features\": [");
+				for (size_t x = 0; x < ARRAY_SIZE(feats); x++) {
+					if (feats[x].supported) {
+						fprintf(fp, "%s\"%s\"",
+							need_sep ? ", " : "",
+							feats[x].name);
+						need_sep = true;
+					}
+				}
+				fprintf(fp, "]");
+			}
+
+			if (pmu->auxtrace) {
+				const struct {
+					bool supported;
+					const char *name;
+				} aux_acts[] = {
+					{ p->can_aux_start_paused, "aux_start_paused" },
+					{ p->can_aux_pause,        "aux_pause" },
+					{ p->can_aux_resume,       "aux_resume" },
+					{ p->can_aux_sample,       "aux_sample" },
+				};
+
+				need_sep = false;
+				fprintf(fp, ",\n\t\t\"AuxActions\": [");
+				for (size_t x = 0; x < ARRAY_SIZE(aux_acts); x++) {
+					if (aux_acts[x].supported) {
+						fprintf(fp, "%s\"%s\"",
+							need_sep ? ", " : "",
+							aux_acts[x].name);
+						need_sep = true;
+					}
+				}
+				fprintf(fp, "]");
+			}
+
+			if (p->can_user_rdpmc && p->pmc_width > 0)
+				fprintf(fp, ",\n\t\t\"PmcWidth\": %u", p->pmc_width);
+
+			fprintf(fp, ",\n\t\t\"UserRdpmc\": {\n");
+			fprintf(fp, "\t\t\t\"Available\": %s,\n",
+				p->rdpmc_available ? "true" : "false");
+			fprintf(fp, "\t\t\t\"Enabled\": %s,\n",
+				p->can_user_rdpmc ? "true" : "false");
+			fprintf(fp, "\t\t\t\"Working\": %s",
+				p->rdpmc_works ? "true" : "false");
+			if (p->rdpmc_available && p->pmc_width > 0)
+				fprintf(fp, ",\n\t\t\t\"PmcWidth\": %u", p->pmc_width);
+			if (p->rdpmc_sysfs >= 0)
+				fprintf(fp, ",\n\t\t\t\"Sysfs\": %d", p->rdpmc_sysfs);
+			fprintf(fp, "\n\t\t}");
+
+			fprintf(fp, ",\n\t\t\"MaxGroupSize\": %d",
+				p->max_group_size);
+			fprintf(fp, ",\n\t\t\"MaxGroupSizeCapped\": %s",
+				p->max_group_capped ? "true" : "false");
+			if (p->max_gp_group_size > 0) {
+				fprintf(fp, ",\n\t\t\"MaxGpGroupSize\": %d",
+					p->max_gp_group_size);
+				fprintf(fp, ",\n\t\t\"MaxGpGroupSizeCapped\": %s",
+					p->max_gp_group_capped ? "true" : "false");
+			}
+		} else {
+			json_escape_fprintf(fp, &buf, ",\n\t\t\"Error\": \"%S\"",
+					    strerror(p->open_err));
+		}
+		fprintf(fp, "\n\t}");
+	}
+
+	fprintf(fp, "\n}");
+	strbuf_release(&buf);
+}
+
+static void print_pmu_text(FILE *fp, const struct perf_pmu *pmu,
+			   const struct perf_session *session, bool show_all)
+{
+	size_t num_instances = show_all ? 1 : perf_pmu__num_duplicates(pmu);
+	char display_name[128];
+	const char *kind_sep = "";
+	size_t total_events, sysfs_events, json_events;
+	struct print_format_state format_state = {
+		.fp = fp,
+		.buf = NULL,
+		.json = false,
+		.count = 0,
+	};
+
+	if (num_instances > 1) {
+		size_t base_len = pmu_name_len_no_suffix(pmu->name ?: "");
+
+		snprintf(display_name, sizeof(display_name), "%.*s",
+			 (int)base_len, pmu->name ?: "");
+	} else {
+		snprintf(display_name, sizeof(display_name), "%s", pmu->name ?: "");
+	}
+
+	color_fprintf(fp, PERF_COLOR_BOLD, "%s", display_name);
+	fprintf(fp, " (");
+	if (pmu->type != PERF_PMU_TYPE_FAKE) {
+		fprintf(fp, "type: %u [%s]", pmu->type, pmu_type_desc(pmu->type));
+		kind_sep = ", ";
+	}
+	if (pmu->is_core) {
+		fprintf(fp, "%s", kind_sep);
+		color_fprintf(fp, PERF_COLOR_GREEN, "core");
+		kind_sep = ", ";
+	}
+	if (pmu->is_uncore) {
+		fprintf(fp, "%s", kind_sep);
+		color_fprintf(fp, PERF_COLOR_CYAN, "uncore");
+		kind_sep = ", ";
+	}
+	if (perf_pmu__is_software(pmu)) {
+		fprintf(fp, "%s", kind_sep);
+		color_fprintf(fp, PERF_COLOR_BLUE, "software");
+		kind_sep = ", ";
+	}
+	if (pmu->auxtrace) {
+		fprintf(fp, "%s", kind_sep);
+		color_fprintf(fp, PERF_COLOR_MAGENTA, "auxtrace");
+		kind_sep = ", ";
+	}
+	if (pmu->selectable) {
+		fprintf(fp, "%sselectable", kind_sep);
+		kind_sep = ", ";
+	}
+	fprintf(fp, "):\n");
+
+	if (num_instances > 1 || (pmu->name && strcmp(pmu->name, display_name) != 0))
+		fprintf(fp, "  Instances: %zu (first: %s)\n", num_instances, pmu->name);
+	if (pmu->alias_name)
+		fprintf(fp, "  Alias: %s\n", pmu->alias_name);
+	if (pmu->id)
+		fprintf(fp, "  Identifier: %s\n", pmu->id);
+	if (pmu->cpus) {
+		char cpus_buf[256];
+		int nr_cpus = perf_cpu_map__nr(pmu->cpus);
+
+		cpu_map__snprint(pmu->cpus, cpus_buf, sizeof(cpus_buf));
+		if (cpus_buf[0]) {
+			if (nr_cpus > 0)
+				fprintf(fp, "  CPUs: %s (%d %s)\n",
+					cpus_buf, nr_cpus,
+					nr_cpus == 1 ? "CPU" : "CPUs");
+			else
+				fprintf(fp, "  CPUs: %s\n", cpus_buf);
+		}
+	}
+
+	perf_pmu__caps_parse((struct perf_pmu *)pmu);
+	if (!list_empty(&pmu->caps)) {
+		struct perf_pmu_caps *cap;
+		bool first = true;
+
+		fprintf(fp, "  Capabilities: ");
+		list_for_each_entry(cap, &pmu->caps, list) {
+			fprintf(fp, "%s%s=%s", first ? "" : ", ", cap->name, cap->value);
+			first = false;
+		}
+		fputc('\n', fp);
+	}
+
+	perf_pmu__for_each_format((struct perf_pmu *)pmu, &format_state, print_format_cb);
+	if (format_state.count > 0)
+		fputc('\n', fp);
+
+	pmu_event_counts((struct perf_pmu *)pmu, &total_events,
+			 &sysfs_events, &json_events);
+	if (total_events > 0) {
+		fprintf(fp, "  Events: %zu total (%zu sysfs, %zu json)\n",
+			total_events, sysfs_events, json_events);
+	}
+
+	print_mem_events_static(fp, NULL, pmu, /*json=*/false);
+	print_recorded_events(fp, NULL, pmu, session, show_all, /*json=*/false);
+
+	if (pmu->probed.checked) {
+		const typeof(pmu->probed) *p = &pmu->probed;
+
+		if (!p->open_supported) {
+			fprintf(fp, "  Probed capabilities: ");
+			if (p->open_err == EACCES && pmu->name &&
+			    !strcmp(pmu->name, "kprobe")) {
+				color_fprintf(fp, PERF_COLOR_YELLOW,
+					      "unavailable (%s; requires CAP_PERFMON or CAP_SYS_ADMIN)",
+					      strerror(p->open_err));
+			} else if (p->open_err == EACCES && pmu->name &&
+				   !strcmp(pmu->name, "uprobe")) {
+				color_fprintf(fp, PERF_COLOR_YELLOW,
+					      "unavailable (%s; requires CAP_SYS_ADMIN)",
+					      strerror(p->open_err));
+			} else {
+				color_fprintf(fp, PERF_COLOR_YELLOW, "unavailable (%s)",
+					      strerror(p->open_err));
+			}
+			fputc('\n', fp);
+		} else {
+			const char *sep = "";
+			const struct {
+				bool supported;
+				const char *name;
+			} excl[] = {
+				{ p->exclude_user,   "exclude_user" },
+				{ p->exclude_kernel, "exclude_kernel" },
+				{ p->exclude_hv,     "exclude_hv" },
+				{ p->exclude_idle,   "exclude_idle" },
+				{ p->exclude_host,   "exclude_host" },
+				{ p->exclude_guest,  "exclude_guest" },
+			};
+
+			fprintf(fp, "  Probed capabilities (event: ");
+			color_fprintf(fp, PERF_COLOR_CYAN, "%s",
+				      p->probe_event ?: "default");
+			fprintf(fp, "):\n");
+			fprintf(fp, "    Modes: ");
+			if (p->can_count) {
+				fprintf(fp, "%s", sep);
+				color_fprintf(fp, PERF_COLOR_GREEN, "counting");
+				sep = ", ";
+			}
+			if (p->can_sample) {
+				fprintf(fp, "%s", sep);
+				color_fprintf(fp, PERF_COLOR_GREEN, "sampling");
+				sep = ", ";
+			}
+			if (p->can_branch_stack) {
+				fprintf(fp, "%s", sep);
+				color_fprintf(fp, PERF_COLOR_GREEN, "branch_stack");
+				sep = ", ";
+			}
+			if (*sep == '\0')
+				color_fprintf(fp, PERF_COLOR_RED, "none");
+			fputc('\n', fp);
+
+			if (p->can_sample) {
+				if (!pmu->auxtrace) {
+					print_precise_levels(fp, pmu, p->precise_mask,
+							     p->max_precise, /*json=*/false);
+
+					print_measured_skid(fp, pmu, /*json=*/false);
+				}
+
+				fprintf(fp, "    Sample types: ");
+				print_sample_type_list(fp, p->sample_types, /*json=*/false);
+				fputc('\n', fp);
+			}
+
+			print_probed_regs(fp, NULL, "User registers",
+					  p->user_regs_mask, p->user_regs_abi,
+					  p->user_simd_mask, p->user_pred_mask,
+					  /*intr=*/false, /*json=*/false);
+			print_probed_regs(fp, NULL, "Intr registers",
+					  p->intr_regs_mask & ~p->intr_regs_precise_mask,
+					  p->intr_regs_abi,
+					  p->intr_simd_mask & ~p->intr_simd_precise_mask,
+					  p->intr_pred_mask & ~p->intr_pred_precise_mask,
+					  /*intr=*/true, /*json=*/false);
+			print_probed_regs(fp, NULL, "Intr registers (precise sampling only)",
+					  p->intr_regs_precise_mask, p->intr_regs_abi,
+					  p->intr_simd_precise_mask, p->intr_pred_precise_mask,
+					  /*intr=*/true, /*json=*/false);
+
+			if (p->mem_events_mask) {
+				sep = "";
+				fprintf(fp, "    Memory events (probed): ");
+				for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
+					struct perf_mem_event *e =
+						perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j);
+
+					if (e && e->tag && (p->mem_events_mask & (1u << j))) {
+						fprintf(fp, "%s%s", sep, e->tag);
+						sep = ", ";
+					}
+				}
+				if (p->mem_sample_types) {
+					fprintf(fp, " (sample types: ");
+					print_sample_type_list(fp, p->mem_sample_types,
+							       /*json=*/false);
+					fprintf(fp, ")");
+				}
+				fputc('\n', fp);
+			}
+
+			sep = "";
+			fprintf(fp, "    Exclusion modifiers: ");
+			for (size_t x = 0; x < ARRAY_SIZE(excl); x++) {
+				if (excl[x].supported) {
+					fprintf(fp, "%s%s", sep, excl[x].name);
+					sep = ", ";
+				}
+			}
+			if (*sep == '\0')
+				fprintf(fp, "none");
+			fputc('\n', fp);
+
+			{
+				const struct {
+					bool supported;
+					const char *name;
+				} feats[] = {
+					{ p->can_leader_sampling,  "leader_sampling" },
+					{ p->can_sw_grouping,      "sw_grouping" },
+					{ p->can_inherit,          "inherit" },
+					{ p->can_freq,             "sample_freq" },
+					{ p->can_write_backward,   "write_backward" },
+					{ p->can_callchain,        "callchain" },
+					{ p->can_data_src,         "data_src" },
+					{ p->can_weight,           "weight" },
+					{ p->can_pinned,           "pinned" },
+					{ p->can_exclusive,        "exclusive" },
+					{ p->can_format_lost,      "format_lost" },
+					{ p->can_acr,              "auto_counter_reload" },
+					{ p->can_aux_start_paused, "aux_start_paused" },
+					{ p->can_aux_pause,        "aux_pause" },
+					{ p->can_aux_resume,       "aux_resume" },
+					{ p->can_aux_sample,       "aux_sample" },
+					{ p->can_user_rdpmc,       "user_rdpmc" },
+					{ p->can_user_time,        "user_time" },
+					{ p->can_user_time_zero,   "user_time_zero" },
+					{ p->can_user_time_short,  "user_time_short" },
+				};
+
+				sep = "";
+				fprintf(fp, "    Features: ");
+				for (size_t x = 0; x < ARRAY_SIZE(feats); x++) {
+					if (feats[x].supported) {
+						fprintf(fp, "%s%s", sep, feats[x].name);
+						sep = ", ";
+					}
+				}
+				if (*sep == '\0')
+					fprintf(fp, "none");
+				fputc('\n', fp);
+			}
+
+			if (pmu->auxtrace) {
+				const struct {
+					bool supported;
+					const char *name;
+				} aux_acts[] = {
+					{ p->can_aux_start_paused, "aux_start_paused" },
+					{ p->can_aux_pause,        "aux_pause" },
+					{ p->can_aux_resume,       "aux_resume" },
+					{ p->can_aux_sample,       "aux_sample" },
+				};
+
+				sep = "";
+				fprintf(fp, "    AUX actions: ");
+				for (size_t x = 0; x < ARRAY_SIZE(aux_acts); x++) {
+					if (aux_acts[x].supported) {
+						fprintf(fp, "%s%s", sep, aux_acts[x].name);
+						sep = ", ";
+					}
+				}
+				if (*sep == '\0')
+					fprintf(fp, "none");
+				fputc('\n', fp);
+			}
+
+			if (pmu->is_core || p->rdpmc_available) {
+				fprintf(fp, "    User space counter reading (rdpmc): ");
+				if (!p->rdpmc_available) {
+					color_fprintf(fp, PERF_COLOR_RED, "not supported");
+					fputc('\n', fp);
+				} else {
+					color_fprintf(fp, PERF_COLOR_GREEN, "available");
+					fprintf(fp, ", ");
+					color_fprintf(fp,
+						      p->can_user_rdpmc ?
+						      PERF_COLOR_GREEN : PERF_COLOR_YELLOW,
+						      "%s",
+						      p->can_user_rdpmc ? "enabled" : "disabled");
+					if (p->rdpmc_sysfs >= 0)
+						fprintf(fp, " (sysfs: %d)", p->rdpmc_sysfs);
+					if (p->can_user_rdpmc) {
+						fprintf(fp, ", ");
+						color_fprintf(fp,
+							      p->rdpmc_works ?
+							      PERF_COLOR_GREEN : PERF_COLOR_RED,
+							      "%s",
+							      p->rdpmc_works ?
+							      "working" : "read failed");
+					}
+					if (p->pmc_width > 0)
+						fprintf(fp, " (width: %u bits)", p->pmc_width);
+					fputc('\n', fp);
+				}
+			}
+
+			fprintf(fp, "    Max event group size: %s%d\n",
+				p->max_group_capped ? ">=" : "",
+				p->max_group_size);
+			if (p->max_gp_group_size > 0 &&
+			    p->max_gp_group_size != p->max_group_size) {
+				fprintf(fp, "    Max GP event group size: %s%d\n",
+					p->max_gp_group_capped ? ">=" : "",
+					p->max_gp_group_size);
+			}
+		}
+	}
+}
+
+static int process_sample_stop(const struct perf_tool *tool __maybe_unused,
+			       union perf_event *event __maybe_unused,
+			       struct perf_sample *sample __maybe_unused,
+			       struct machine *machine __maybe_unused)
+{
+	session_done = 1;
+	return 0;
+}
+
+#define MAX_SUMMARY_METRICS 2048
+#define MAX_DRILLDOWN_LEVELS 16
+
+struct summary_metric_item {
+	char *pmu;
+	char *name;
+	char *groups;
+	int level;
+	bool has_children;
+	bool has_parent;
+};
+
+struct summary_metrics_state {
+	struct strlist *metrics;
+	struct strlist *groups;
+	struct summary_metric_item items[MAX_SUMMARY_METRICS];
+	size_t nr_items;
+};
+
+static bool group_list_has_token(const char *groups, const char *token)
+{
+	size_t tok_len;
+	const char *p;
+
+	if (!groups || !token || !*token)
+		return false;
+	tok_len = strlen(token);
+	p = groups;
+	while (*p) {
+		const char *semi = strchr(p, ';');
+		size_t len = semi ? (size_t)(semi - p) : strlen(p);
+
+		if (len == tok_len && !strncmp(p, token, len))
+			return true;
+		if (!semi)
+			break;
+		p = semi + 1;
+	}
+	return false;
+}
+
+static int collect_summary_metric_cb(const struct pmu_metric *pm,
+				     const struct pmu_metrics_table *table __maybe_unused,
+				     void *vdata)
+{
+	struct summary_metrics_state *st = vdata;
+
+	if (pm->metric_name)
+		strlist__add(st->metrics, pm->metric_name);
+
+	if (pm->metric_group) {
+		char *copy = strdup(pm->metric_group);
+		char *tok, *saveptr = NULL;
+
+		if (copy) {
+			for (tok = strtok_r(copy, ";", &saveptr); tok;
+			     tok = strtok_r(NULL, ";", &saveptr)) {
+				if (*tok)
+					strlist__add(st->groups, tok);
+			}
+			free(copy);
+		}
+	}
+
+	if (pm->metric_name) {
+		const char *pmu_name = pm->pmu ?: "cpu";
+		struct summary_metric_item *existing = NULL;
+
+		for (size_t i = 0; i < st->nr_items; i++) {
+			if (st->items[i].pmu && st->items[i].name &&
+			    !strcmp(st->items[i].pmu, pmu_name) &&
+			    !strcmp(st->items[i].name, pm->metric_name)) {
+				existing = &st->items[i];
+				break;
+			}
+		}
+		if (!existing && st->nr_items < MAX_SUMMARY_METRICS) {
+			existing = &st->items[st->nr_items++];
+			existing->pmu = strdup(pmu_name);
+			existing->name = strdup(pm->metric_name);
+			existing->groups = strdup(pm->metric_group ?: "");
+		} else if (existing && pm->metric_group && existing->groups &&
+			   !strstr(existing->groups, pm->metric_group)) {
+			char *merged = NULL;
+
+			if (asprintf(&merged, "%s;%s", existing->groups, pm->metric_group) > 0) {
+				free(existing->groups);
+				existing->groups = merged;
+			}
+		}
+	}
+	return 0;
+}
+
+static int compute_metric_drilldown(struct summary_metrics_state *st,
+				    int level_counts[MAX_DRILLDOWN_LEVELS],
+				    char *level_labels[MAX_DRILLDOWN_LEVELS])
+{
+	int max_level = 0;
+
+	memset(level_counts, 0, sizeof(int) * MAX_DRILLDOWN_LEVELS);
+	memset(level_labels, 0, sizeof(char *) * MAX_DRILLDOWN_LEVELS);
+
+	for (size_t i = 0; i < st->nr_items; i++) {
+		char child_grp[256];
+
+		if (!st->items[i].name || !st->items[i].pmu)
+			continue;
+		snprintf(child_grp, sizeof(child_grp), "%s_group", st->items[i].name);
+		if (!strlist__has_entry(st->groups, child_grp))
+			continue;
+
+		for (size_t j = 0; j < st->nr_items; j++) {
+			if (i == j || !st->items[j].groups || !st->items[j].pmu)
+				continue;
+			if (strcmp(st->items[i].pmu, st->items[j].pmu) != 0)
+				continue;
+			if (group_list_has_token(st->items[j].groups, child_grp)) {
+				st->items[i].has_children = true;
+				st->items[j].has_parent = true;
+			}
+		}
+	}
+
+	for (size_t i = 0; i < st->nr_items; i++) {
+		if (st->items[i].has_children && !st->items[i].has_parent) {
+			st->items[i].level = 1;
+			max_level = 1;
+		}
+	}
+
+	for (int d = 1; d < MAX_DRILLDOWN_LEVELS - 1; d++) {
+		bool progress = false;
+
+		for (size_t i = 0; i < st->nr_items; i++) {
+			char child_grp[256];
+
+			if (st->items[i].level != d || !st->items[i].has_children)
+				continue;
+			snprintf(child_grp, sizeof(child_grp), "%s_group", st->items[i].name);
+			for (size_t j = 0; j < st->nr_items; j++) {
+				if (i == j || !st->items[j].groups || !st->items[j].pmu)
+					continue;
+				if (strcmp(st->items[i].pmu, st->items[j].pmu) != 0)
+					continue;
+				if (group_list_has_token(st->items[j].groups, child_grp) &&
+				    st->items[j].level < d + 1) {
+					st->items[j].level = d + 1;
+					if (d + 1 > max_level)
+						max_level = d + 1;
+					progress = true;
+				}
+			}
+		}
+		if (!progress)
+			break;
+	}
+
+	for (int d = 1; d <= max_level; d++) {
+		struct str_node *pos;
+		const char *best_label = NULL;
+		int best_hits = 0;
+		int total_at_d = 0;
+
+		for (size_t i = 0; i < st->nr_items; i++) {
+			if (st->items[i].level == d)
+				total_at_d++;
+		}
+		level_counts[d] = total_at_d;
+
+		strlist__for_each_entry(pos, st->groups) {
+			const char *g = pos->s;
+			size_t glen = strlen(g);
+			int hits_in_d = 0;
+			bool in_other_d = false;
+
+			if (!strcasecmp(g, "Default") || !strcasecmp(g, "Summary") ||
+			    (glen > 6 && !strcmp(g + glen - 6, "_group")))
+				continue;
+
+			for (size_t i = 0; i < st->nr_items; i++) {
+				if (!st->items[i].groups || st->items[i].level == 0)
+					continue;
+				if (group_list_has_token(st->items[i].groups, g)) {
+					if (st->items[i].level == d)
+						hits_in_d++;
+					else
+						in_other_d = true;
+				}
+			}
+
+			if (!in_other_d &&
+			    (hits_in_d > best_hits ||
+			     (hits_in_d == best_hits && best_label &&
+			      strlen(g) > strlen(best_label)))) {
+				best_hits = hits_in_d;
+				best_label = g;
+			}
+		}
+
+		if (best_label && best_hits * 2 >= total_at_d)
+			level_labels[d] = strdup(best_label);
+	}
+
+	return max_level;
+}
+
+static bool is_mem_bw_pmu_name(const char *name)
+{
+	if (!name)
+		return false;
+	return strstarts(name, "uncore_imc") ||
+	       strstarts(name, "uncore_mc") ||
+	       strstarts(name, "uncore_edc") ||
+	       strstarts(name, "uncore_mchbm") ||
+	       strstarts(name, "amd_df") ||
+	       strstarts(name, "amd_umc") ||
+	       strstarts(name, "arm_dmc") ||
+	       strstarts(name, "dmc620") ||
+	       strstarts(name, "arm_cmn") ||
+	       strstr(name, "ddrc") != NULL ||
+	       strstr(name, "ddr") != NULL ||
+	       !strcmp(name, "hv_24x7") ||
+	       strstarts(name, "nest_mc");
+}
+
+static bool is_io_bw_pmu_name(const char *name)
+{
+	if (!name)
+		return false;
+	return strstarts(name, "uncore_iio") ||
+	       strstarts(name, "uncore_irp") ||
+	       strstarts(name, "uncore_pcie") ||
+	       strstarts(name, "uncore_upi") ||
+	       strstarts(name, "uncore_qpi") ||
+	       strstarts(name, "uncore_m2pcie") ||
+	       strstarts(name, "uncore_m3upi") ||
+	       strstarts(name, "uncore_arb") ||
+	       strstarts(name, "cxl") ||
+	       strstarts(name, "amd_iommu") ||
+	       strstarts(name, "amd_xgmi") ||
+	       strstarts(name, "arm_smmu") ||
+	       strstr(name, "pcie") != NULL ||
+	       strstarts(name, "dwc_rootport") ||
+	       strstarts(name, "cci") ||
+	       strstarts(name, "ccn");
+}
+
+static void print_strlist_color(FILE *fp, const char *color, struct strlist *list)
+{
+	struct str_node *pos;
+	bool first = true;
+
+	strlist__for_each_entry(pos, list) {
+		if (!first)
+			fprintf(fp, ", ");
+		color_fprintf(fp, color, "%s", pos->s);
+		first = false;
+	}
+}
+
+static void add_pmu_display_name(struct strlist *list, const struct perf_pmu *pmu)
+{
+	size_t num_inst = perf_pmu__num_duplicates(pmu);
+	char buf[128];
+
+	if (!pmu->name)
+		return;
+	if (num_inst > 1) {
+		size_t base_len = pmu_name_len_no_suffix(pmu->name);
+
+		snprintf(buf, sizeof(buf), "%.*s", (int)base_len, pmu->name);
+		strlist__add(list, buf);
+	} else {
+		strlist__add(list, pmu->name);
+	}
+}
+
+static void print_system_summary(FILE *fp, const struct perf_session *session,
+				 bool probed, bool leading_newline)
+{
+	struct perf_env *env = session ? (struct perf_env *)&session->header.env : host_env();
+	/* As in cmd_sysinfo(), use the PMUs read from the perf.data file. */
+	struct perf_pmus *pmus = session ? &env->pmus : perf_env__pmus(env);
+	const struct pmu_metrics_table *mtable;
+	struct perf_pmu *pmu = NULL;
+	struct strlist *core_pmus = strlist__new(NULL, NULL);
+	struct strlist *mem_bw_pmus = strlist__new(NULL, NULL);
+	struct strlist *io_bw_pmus = strlist__new(NULL, NULL);
+	struct strlist *aux_pmus = strlist__new(NULL, NULL);
+	struct strlist *missing = strlist__new(NULL, NULL);
+	struct summary_metrics_state *mstate = zalloc(sizeof(*mstate));
+	int level_counts[MAX_DRILLDOWN_LEVELS];
+	char *level_labels[MAX_DRILLDOWN_LEVELS];
+	int max_drill_lvl = 0;
+	bool has_sw_sampling = false;
+	bool core_probed = false;
+	bool core_can_sample = false;
+	bool core_precise = false;
+	int max_precise = 0;
+	bool core_rdpmc = false;
+	bool core_branch_stack = false;
+	int max_br_depth = 0;
+	bool core_mem_events = false;
+	bool core_leader_sampling = false;
+	size_t total_pmus = 0, total_events = 0, total_sysfs_events = 0, total_json_events = 0;
+	size_t nr_core, nr_metrics, nr_groups;
+
+	if (!mstate)
+		goto out_free;
+	mstate->metrics = strlist__new(NULL, NULL);
+	mstate->groups = strlist__new(NULL, NULL);
+
+	if (!core_pmus || !mem_bw_pmus || !io_bw_pmus || !aux_pmus ||
+	    !missing || !mstate->metrics || !mstate->groups)
+		goto out_free;
+
+	while ((pmu = perf_pmus__scan_skip_duplicates(pmus, pmu)) != NULL) {
+		size_t pmu_events, sysfs_ev, json_ev;
+
+		pmu_event_counts(pmu, &pmu_events, &sysfs_ev, &json_ev);
+
+		total_pmus++;
+		total_events += pmu_events;
+		total_sysfs_events += sysfs_ev;
+		total_json_events += json_ev;
+
+		if (pmu->is_core &&
+		    (pmu->from_env || sysfs_ev > 0 || !list_empty(&pmu->format) ||
+		     (probed && pmu->probed.checked && pmu->probed.open_supported))) {
+			struct perf_pmu_caps *br_cap;
+
+			add_pmu_display_name(core_pmus, pmu);
+			perf_pmu__caps_parse(pmu);
+			br_cap = perf_pmu__get_cap(pmu, "branches");
+			if (br_cap && br_cap->value) {
+				int depth = atoi(br_cap->value);
+
+				if (depth > max_br_depth)
+					max_br_depth = depth;
+			}
+			if (probed && pmu->probed.checked) {
+				core_probed = true;
+				if (pmu->probed.open_supported && pmu->probed.can_sample)
+					core_can_sample = true;
+				if (pmu->probed.open_supported && pmu->probed.max_precise > 0) {
+					core_precise = true;
+					if (pmu->probed.max_precise > max_precise)
+						max_precise = pmu->probed.max_precise;
+				}
+				if (pmu->probed.open_supported && pmu->probed.can_user_rdpmc)
+					core_rdpmc = true;
+				if (pmu->probed.open_supported && pmu->probed.can_branch_stack)
+					core_branch_stack = true;
+				if (pmu->probed.open_supported && pmu->probed.can_leader_sampling)
+					core_leader_sampling = true;
+			} else {
+				core_can_sample = true;
+				if (pmu->max_precise > 0) {
+					core_precise = true;
+					if (pmu->max_precise > max_precise)
+						max_precise = pmu->max_precise;
+				}
+				if (max_br_depth > 0)
+					core_branch_stack = true;
+			}
+		} else if (pmu->type == PERF_TYPE_SOFTWARE) {
+			if (!probed || !pmu->probed.checked || pmu->probed.can_sample)
+				has_sw_sampling = true;
+		}
+
+		if (pmu->mem_events) {
+			if (probed && pmu->probed.checked) {
+				if (pmu->probed.open_supported && pmu->probed.mem_events_mask)
+					core_mem_events = true;
+			} else if (!session) {
+				perf_pmu__mem_events_init_pmu(pmu);
+				for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
+					struct perf_mem_event *e =
+						perf_pmu__mem_events_ptr(pmu, j);
+
+					if (e && e->tag && e->supported) {
+						core_mem_events = true;
+						break;
+					}
+				}
+			}
+		}
+
+		if (pmu->auxtrace)
+			add_pmu_display_name(aux_pmus, pmu);
+		if (is_mem_bw_pmu_name(pmu->name))
+			add_pmu_display_name(mem_bw_pmus, pmu);
+		if (is_io_bw_pmu_name(pmu->name))
+			add_pmu_display_name(io_bw_pmus, pmu);
+	}
+
+	if (session) {
+		mtable = env->cpuid ?
+			 find_core_metrics_table(perf_env__e_machine(env, /*e_flags=*/NULL),
+						 env->cpuid) : NULL;
+	} else {
+		mtable = pmu_metrics_table__find();
+	}
+
+	metricgroup__for_each_metric(pmus, mtable, collect_summary_metric_cb, mstate);
+	max_drill_lvl = compute_metric_drilldown(mstate, level_counts, level_labels);
+
+	nr_core = strlist__nr_entries(core_pmus);
+	nr_metrics = strlist__nr_entries(mstate->metrics);
+	nr_groups = strlist__nr_entries(mstate->groups);
+
+	if (leading_newline)
+		fputc('\n', fp);
+	color_fprintf(fp, PERF_COLOR_BOLD, "Summary:");
+	fputc('\n', fp);
+	if (nr_core == 0) {
+		const char *sw_desc = has_sw_sampling ?
+			"minimal system; sampling only via software timer events" :
+			"no sampling support detected";
+
+		color_fprintf(fp, PERF_COLOR_BOLD, "  Core PMUs:");
+		fprintf(fp, " ");
+		color_fprintf(fp, PERF_COLOR_RED, "none");
+		fprintf(fp, " (%s)\n", sw_desc);
+		strlist__add(missing, "hardware core PMU");
+	} else {
+		color_fprintf(fp, PERF_COLOR_BOLD, "  Core PMUs (%zu%s):", nr_core,
+			      nr_core > 1 ? ", hybrid" : "");
+		fprintf(fp, " ");
+		print_strlist_color(fp, PERF_COLOR_GREEN, core_pmus);
+		fputc('\n', fp);
+
+		color_fprintf(fp, PERF_COLOR_BOLD, "  Core features:");
+		fputc('\n', fp);
+		fprintf(fp, "    Interrupt-based sampling: ");
+		color_fprintf(fp, core_can_sample ? PERF_COLOR_GREEN : PERF_COLOR_RED,
+			      "%s", core_can_sample ? "supported" : "missing");
+		fputc('\n', fp);
+		if (!core_can_sample)
+			strlist__add(missing, "core interrupt sampling");
+
+		fprintf(fp, "    Precise event sampling: ");
+		if (core_precise) {
+			color_fprintf(fp, PERF_COLOR_GREEN, "supported");
+			fprintf(fp, " (max_precise: %d [%s])\n",
+				max_precise, precise_level_desc(max_precise));
+		} else {
+			color_fprintf(fp, PERF_COLOR_RED, "missing");
+			fprintf(fp, " (%s)\n", precise_level_desc(0));
+			strlist__add(missing, "precise event sampling (PEBS/IBS/SPE)");
+		}
+
+		fprintf(fp, "    Branch stack sampling (LBR/BRBE): ");
+		if (core_branch_stack) {
+			color_fprintf(fp, PERF_COLOR_GREEN, "supported");
+			if (max_br_depth > 0)
+				fprintf(fp, " (depth: %d branches)\n", max_br_depth);
+			else
+				fputc('\n', fp);
+		} else {
+			color_fprintf(fp, PERF_COLOR_RED, "missing");
+			fputc('\n', fp);
+			strlist__add(missing, "hardware branch stack sampling (LBR/BRBE)");
+		}
+
+		if (core_probed) {
+			fprintf(fp, "    User-space counter reading (rdpmc): ");
+			color_fprintf(fp, core_rdpmc ? PERF_COLOR_GREEN : PERF_COLOR_YELLOW,
+				      "%s", core_rdpmc ? "supported" : "missing/disabled");
+			fputc('\n', fp);
+			if (!core_rdpmc)
+				strlist__add(missing, "user-space counter reading (rdpmc)");
+
+			fprintf(fp, "    Leader sampling: ");
+			color_fprintf(fp,
+				      core_leader_sampling ? PERF_COLOR_GREEN : PERF_COLOR_RED,
+				      "%s",
+				      core_leader_sampling ? "supported" : "missing");
+			fputc('\n', fp);
+		}
+
+		fprintf(fp, "    Memory profiling events (DATA_SRC): ");
+		color_fprintf(fp, core_mem_events ? PERF_COLOR_GREEN : PERF_COLOR_RED,
+			      "%s", core_mem_events ? "supported" : "missing");
+		fputc('\n', fp);
+		if (!core_mem_events)
+			strlist__add(missing, "memory load/store profiling events");
+
+		fprintf(fp, "    Hardware AUX trace PMUs: ");
+		if (!strlist__empty(aux_pmus)) {
+			print_strlist_color(fp, PERF_COLOR_GREEN, aux_pmus);
+			fputc('\n', fp);
+		} else {
+			color_fprintf(fp, PERF_COLOR_RED, "missing");
+			fputc('\n', fp);
+			strlist__add(missing, "hardware AUX trace PMU");
+		}
+	}
+
+	color_fprintf(fp, PERF_COLOR_BOLD, "  Bus & uncore monitoring:");
+	fputc('\n', fp);
+	fprintf(fp, "    Memory bandwidth PMUs: ");
+	if (!strlist__empty(mem_bw_pmus)) {
+		print_strlist_color(fp, PERF_COLOR_GREEN, mem_bw_pmus);
+		fputc('\n', fp);
+	} else {
+		color_fprintf(fp, PERF_COLOR_RED, "missing");
+		fputc('\n', fp);
+		strlist__add(missing, "memory bus bandwidth PMUs");
+	}
+
+	fprintf(fp, "    PCIe / IO interconnect bandwidth PMUs: ");
+	if (!strlist__empty(io_bw_pmus)) {
+		print_strlist_color(fp, PERF_COLOR_GREEN, io_bw_pmus);
+		fputc('\n', fp);
+	} else {
+		color_fprintf(fp, PERF_COLOR_RED, "missing");
+		fputc('\n', fp);
+		strlist__add(missing, "PCIe/IO interconnect bandwidth PMUs");
+	}
+
+	color_fprintf(fp, PERF_COLOR_BOLD, "  Events & metrics:");
+	fputc('\n', fp);
+	fprintf(fp, "    Available events: %zu total (%zu sysfs, %zu json) across %zu PMUs\n",
+		total_events, total_sysfs_events, total_json_events, total_pmus);
+	if (nr_metrics == 0) {
+		fprintf(fp, "    Available metrics: ");
+		color_fprintf(fp, PERF_COLOR_RED, "none");
+		fprintf(fp, " (missing JSON metrics)\n");
+		strlist__add(missing, "multi-level pipeline drill-down and JSON metrics");
+	} else {
+		const char *tier = (max_drill_lvl >= 2) ? "advanced" : "minimal";
+		const char *tier_col = (max_drill_lvl >= 2) ?
+				       PERF_COLOR_GREEN : PERF_COLOR_YELLOW;
+
+		fprintf(fp, "    Available metrics: %zu metrics in %zu groups (",
+			nr_metrics, nr_groups);
+		color_fprintf(fp, tier_col, "%s", tier);
+		fprintf(fp, ")\n");
+		if (max_drill_lvl > 0) {
+			fprintf(fp, "    Metric drill-down levels (%d): ", max_drill_lvl);
+			for (int d = 1; d <= max_drill_lvl; d++) {
+				if (d > 1)
+					fprintf(fp, ", ");
+				color_fprintf(fp, PERF_COLOR_CYAN, "L%d", d);
+				if (level_labels[d])
+					fprintf(fp, " (%s: %d metrics)",
+						level_labels[d], level_counts[d]);
+				else
+					fprintf(fp, " (%d metrics)", level_counts[d]);
+			}
+			fputc('\n', fp);
+		} else {
+			fprintf(fp, "    Metric drill-down levels: ");
+			color_fprintf(fp, PERF_COLOR_YELLOW, "none");
+			fprintf(fp, " (flat metrics only)\n");
+		}
+		if (max_drill_lvl < 2) {
+			strlist__add(missing,
+				     "multi-level pipeline drill-down metrics (<metric>_group)");
+		}
+	}
+
+	color_fprintf(fp, PERF_COLOR_BOLD, "  System rating:");
+	if (nr_core == 0) {
+		const char *min_desc =
+			"no hardware core PMU; profiling limited to software timer events";
+
+		fprintf(fp, " ");
+		color_fprintf(fp, PERF_COLOR_RED, "Minimal");
+		fprintf(fp, " (%s)\n", min_desc);
+	} else if (core_precise && core_branch_stack && (!core_probed || core_rdpmc) &&
+		   !strlist__empty(mem_bw_pmus) && max_drill_lvl >= 2) {
+		const char *core_adv = "precise sampling, branch stack (LBR), user rdpmc";
+		const char *sys_adv = "bus PMUs, and metric drill-down";
+
+		fprintf(fp, " ");
+		color_fprintf(fp, PERF_COLOR_GREEN, "Advanced");
+		fprintf(fp, " (core PMU with %s, %s)\n", core_adv, sys_adv);
+	} else if (core_can_sample &&
+		   (core_precise || core_branch_stack || max_drill_lvl >= 1)) {
+		const char *std_desc =
+			"core PMU with hardware sampling and partial advanced features or metrics";
+
+		fprintf(fp, " ");
+		color_fprintf(fp, PERF_COLOR_CYAN, "Standard");
+		fprintf(fp, " (%s)\n", std_desc);
+	} else {
+		const char *bas_desc =
+			"core PMU with basic counting/sampling only; missing advanced features";
+
+		fprintf(fp, " ");
+		color_fprintf(fp, PERF_COLOR_YELLOW, "Basic");
+		fprintf(fp, " (%s)\n", bas_desc);
+	}
+
+	if (!strlist__empty(missing)) {
+		color_fprintf(fp, PERF_COLOR_BOLD, "  Missing baseline features:");
+		fprintf(fp, " ");
+		print_strlist_color(fp, PERF_COLOR_RED, missing);
+		fputc('\n', fp);
+	}
+
+	for (int d = 1; d <= max_drill_lvl; d++)
+		free(level_labels[d]);
+
+out_free:
+	strlist__delete(core_pmus);
+	strlist__delete(mem_bw_pmus);
+	strlist__delete(io_bw_pmus);
+	strlist__delete(aux_pmus);
+	strlist__delete(missing);
+	if (mstate) {
+		for (size_t i = 0; i < mstate->nr_items; i++) {
+			free(mstate->items[i].pmu);
+			free(mstate->items[i].name);
+			free(mstate->items[i].groups);
+		}
+		strlist__delete(mstate->metrics);
+		strlist__delete(mstate->groups);
+		free(mstate);
+	}
+}
+
+int cmd_sysinfo(int argc, const char **argv)
+{
+	bool json = false;
+	bool show_pmus = false;
+	bool show_all = false;
+	bool force = false;
+	bool do_probe = true;
+	const char *output_path = NULL;
+	const char *input_name = NULL;
+	FILE *fp = stdout;
+	struct perf_data data = {
+		.mode = PERF_DATA_MODE_READ,
+	};
+	struct perf_tool tool;
+	struct perf_session *session = NULL;
+	struct perf_pmus *pmus;
+	struct perf_pmu *pmu = NULL;
+	size_t pmu_idx = 0;
+	int ret = 0;
+	const struct option sysinfo_options[] = {
+		OPT_STRING('i', "input", &input_name, "file",
+			   "Input perf.data file name to read PMUs from"),
+		OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
+		OPT_STRING('o', "output", &output_path, "file", "output file name"),
+		OPT_BOOLEAN('p', "pmus", &show_pmus,
+			    "Display detailed information for each PMU"),
+		OPT_BOOLEAN('j', "json", &json, "JSON encode PMU information"),
+		OPT_BOOLEAN('v', "verbose", &show_all,
+			    "Show all similar PMUs with alphanumeric suffixes"),
+		OPT_BOOLEAN('a', "all", &show_all,
+			    "Show all similar PMUs with alphanumeric suffixes"),
+		OPT_BOOLEAN(0, "probe", &do_probe,
+			    "Probe PMU capabilities on live system (default: on)"),
+		OPT_INCR(0, "debug", &verbose, "Enable debugging output"),
+		OPT_END()
+	};
+	const char * const sysinfo_usage[] = {
+		"perf sysinfo [<options>] [pmu_glob ...]",
+		NULL
+	};
+
+	argc = parse_options(argc, argv, sysinfo_options, sysinfo_usage,
+			     PARSE_OPT_STOP_AT_NON_OPTION);
+
+	if (show_all || json || argc > 0)
+		show_pmus = true;
+
+	if (output_path) {
+		perf_use_color_default = 0;
+		fp = fopen(output_path, "w");
+		if (!fp) {
+			pr_err("Failed to open output file '%s': %m\n", output_path);
+			return -1;
+		}
+	}
+
+	if (input_name) {
+		data.path = input_name;
+		data.force = force;
+
+		perf_tool__init(&tool, /*ordered_events=*/false);
+		tool.attr = perf_event__process_attr;
+		/*
+		 * In pipe mode event names follow the header features in
+		 * event update records, so process events until the first
+		 * sample.
+		 */
+		tool.event_update = perf_event__process_event_update;
+		tool.feature = perf_event__process_feature;
+		tool.sample = process_sample_stop;
+
+		session = perf_session__new(&data, &tool);
+		if (IS_ERR(session)) {
+			ret = PTR_ERR(session);
+			pr_err("Error opening %s: %s\n", input_name, strerror(-ret));
+			goto out_close;
+		}
+
+		if (data.is_pipe)
+			perf_session__process_events(session);
+
+		ret = perf_pmus__read_from_env(&session->header.env.pmus,
+					       &session->header.env);
+		if (ret) {
+			pr_err("Failed to read PMU information from %s: %s\n",
+			       input_name, strerror(-ret));
+			goto out_session;
+		}
+	}
+
+	/*
+	 * Use the PMUs read from the perf.data file even if it lacks PMU
+	 * header features, in which case perf_env__pmus() would return the
+	 * live host's PMUs.
+	 */
+	pmus = session ? &session->header.env.pmus : perf_env__pmus(host_env());
+
+	if (json)
+		fputs("[", fp);
+
+	while ((pmu = show_all ? perf_pmus__scan(pmus, pmu) :
+				 perf_pmus__scan_skip_duplicates(pmus, pmu)) != NULL) {
+		if (!pmu_matches_filters(pmu, show_all, argc, argv))
+			continue;
+
+		if (do_probe && !input_name &&
+		    (show_pmus || pmu->is_core || pmu->mem_events ||
+		     pmu->type == PERF_TYPE_SOFTWARE))
+			perf_pmu__probe(pmu);
+
+		if (!show_pmus)
+			continue;
+
+		if (json) {
+			if (pmu_idx > 0)
+				fputs(",\n", fp);
+			else
+				fputs("\n", fp);
+			print_pmu_json(fp, pmu, session, show_all);
+		} else {
+			if (pmu_idx > 0)
+				fputc('\n', fp);
+			print_pmu_text(fp, pmu, session, show_all);
+		}
+		pmu_idx++;
+	}
+
+	if (json)
+		fputs(pmu_idx > 0 ? "\n]\n" : "]\n", fp);
+	else if (argc == 0)
+		print_system_summary(fp, session, do_probe && !input_name,
+				     pmu_idx > 0);
+
+out_session:
+	if (session)
+		perf_session__delete(session);
+out_close:
+	if (output_path && fp)
+		fclose(fp);
+	return ret;
+}
diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h
index e0b430821abc..28e85c054468 100644
--- a/tools/perf/builtin.h
+++ b/tools/perf/builtin.h
@@ -37,6 +37,7 @@ int cmd_list(int argc, const char **argv);
 int cmd_record(int argc, const char **argv);
 int cmd_report(int argc, const char **argv);
 int cmd_stat(int argc, const char **argv);
+int cmd_sysinfo(int argc, const char **argv);
 int cmd_timechart(int argc, const char **argv);
 int cmd_top(int argc, const char **argv);
 int cmd_script(int argc, const char **argv);
diff --git a/tools/perf/perf.c b/tools/perf/perf.c
index bcbc12295a38..f4ac149e9e8c 100644
--- a/tools/perf/perf.c
+++ b/tools/perf/perf.c
@@ -113,6 +113,7 @@ static const struct cmd_struct commands[] = {
 	{ "report",	cmd_report,	0 },
 	{ "bench",	cmd_bench,	0 },
 	{ "stat",	cmd_stat,	0 },
+	{ "sysinfo",	cmd_sysinfo,	0 },
 	{ "timechart",	cmd_timechart,	0 },
 	{ "top",	cmd_top,	0 },
 	{ "annotate",	cmd_annotate,	0 },
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 40/41] perf docs: Add perf-sysinfo man page
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (38 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 39/41] perf sysinfo: Add sysinfo subcommand Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  2026-11-03 23:44 ` [PATCH v1 41/41] perf test: Add perf sysinfo shell test Ian Rogers
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Document the 'perf sysinfo' subcommand and its options in
Documentation/perf-sysinfo.txt and link it from Documentation/perf.txt.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/Documentation/perf-sysinfo.txt | 113 ++++++++++++++++++++++
 tools/perf/Documentation/perf.txt         |   2 +-
 2 files changed, 114 insertions(+), 1 deletion(-)
 create mode 100644 tools/perf/Documentation/perf-sysinfo.txt

diff --git a/tools/perf/Documentation/perf-sysinfo.txt b/tools/perf/Documentation/perf-sysinfo.txt
new file mode 100644
index 000000000000..c72ac6d51551
--- /dev/null
+++ b/tools/perf/Documentation/perf-sysinfo.txt
@@ -0,0 +1,113 @@
+perf-sysinfo(1)
+===============
+
+NAME
+----
+perf-sysinfo - Display and probe PMU and system performance monitoring information
+
+SYNOPSIS
+--------
+[verse]
+'perf sysinfo' [<options>] [pmu_glob ...]
+
+DESCRIPTION
+-----------
+This command displays a summary of the performance monitoring capabilities of
+the current system or a perf.data file, including core PMU features (precise
+sampling, branch stack sampling, user-space counter reading, memory profiling,
+and hardware AUX trace PMUs), memory and PCIe/IO interconnect bus bandwidth
+monitoring support, total events and metrics, pipeline metric drill-down levels
+discovered via '<metric>_group' hierarchies, and an overall system rating.
+
+When '-p'/'--pmus' is specified (or when PMU filter globs, '-v'/'--verbose',
+'-a'/'--all', or '-j'/'--json' are given), it also displays detailed
+information for each PMU, including its perf_event_attr type, kind (core,
+uncore, software, auxtrace, tool, hwmon, drm), CPUs/cpumask, capabilities,
+sysfs format bitfields, event counts, and memory profiling events.
+
+When run on a live system (without '-i'/'--input' and with probing enabled),
+it also actively probes the kernel via 'perf_event_open' to determine:
+- Supported modes (counting, interrupt-based sampling, branch_stack sampling)
+- Supported precise sampling levels, skid semantics, and empirically measured
+  branch instruction skid (minimum, maximum, average instructions from the
+  branch and 'PERF_RECORD_MISC_EXACT_IP' ratio) at each supported precision
+  level:
+  0 ('SAMPLE_IP' can have arbitrary skid), ':p' / 1 ('SAMPLE_IP' must have
+  constant skid), ':pp' / 2 ('SAMPLE_IP' requested to have 0 skid), and
+  ':ppp' / 3 ('SAMPLE_IP' must have 0 skid, or uses randomization to avoid
+  sample shadowing effects)
+- Supported 'perf_event_sample_format' sample types ('ip', 'tid', 'time',
+  'addr', 'read', 'callchain', 'id', 'cpu', 'period', 'stream_id', 'raw',
+  'branch_stack', 'regs_user', 'stack_user', 'weight', 'data_src',
+  'identifier', 'transaction', 'regs_intr', 'phys_addr', 'aux', 'cgroup',
+  'data_page_size', 'code_page_size', 'weight_struct')
+- Supported user and interrupt registers for 'PERF_SAMPLE_REGS_USER' and
+  'PERF_SAMPLE_REGS_INTR', listing separately the interrupt registers that
+  can only be sampled with precise sampling (e.g. x86 XMM registers captured
+  by PEBS)
+- Supported memory profiling events ('ldlat-loads', 'ldlat-stores', 'mem-ldst')
+  and their supported memory sample types ('data_src', 'addr', 'weight', etc.)
+- Supported privilege/exclusion modifiers ('exclude_user', 'exclude_kernel',
+  'exclude_hv', 'exclude_idle', 'exclude_host', 'exclude_guest')
+- Supported execution features ('leader_sampling', 'sw_grouping', 'inherit',
+  'sample_freq', 'write_backward', 'callchain', 'data_src', 'weight',
+  'pinned', 'exclusive', 'format_lost', 'auto_counter_reload',
+  'aux_start_paused', 'aux_pause', 'aux_resume', 'aux_sample', 'user_rdpmc',
+  'user_time', 'user_time_zero', 'user_time_short') and hardware counter
+  bit-width ('pmc_width') when user-space counter reading is supported
+- Maximum number of events in a group counted simultaneously, without
+  multiplexing, and for core PMUs the maximum number of general-purpose
+  counter events. Groups larger than this may be accepted by
+  'perf_event_open' but can't be counted together
+
+At the end of the text output, a summary section reports overall core PMU
+capabilities (including hardware AUX trace PMUs), memory and PCIe/IO
+interconnect bus bandwidth monitoring support, total events and metrics, and
+pipeline metric drill-down levels discovered via '<metric>_group' hierarchies.
+
+By default, numbered uncore PMU instances sharing a common prefix are collapsed
+into a single entry showing the instance count, unless '-v'/'--verbose' is
+specified.
+
+OPTIONS
+-------
+-i::
+--input=::
+	Input file name to read PMU information from (e.g. perf.data).
+
+-f::
+--force::
+	Don't complain, do it (when reading input perf.data file).
+
+-o::
+--output=::
+	Output file name.
+
+-p::
+--pmus::
+	Display detailed information for each PMU in addition to the system
+	summary.
+
+-j::
+--json::
+	Encode PMU information as JSON.
+
+-a::
+--all::
+-v::
+--verbose::
+	Display all PMUs including all similar PMUs with alphanumeric suffixes
+	instead of collapsing numbered uncore PMU instances.
+
+--probe::
+--no-probe::
+	Enable or disable active kernel probing of PMU capabilities on the live
+	system (default: enabled).
+
+--debug::
+	Enable debugging output.
+
+SEE ALSO
+--------
+linkperf:perf-list[1], linkperf:perf-evlist[1], linkperf:perf-stat[1],
+linkperf:perf-record[1]
diff --git a/tools/perf/Documentation/perf.txt b/tools/perf/Documentation/perf.txt
index cbcc2e4d557e..0890dd5a1794 100644
--- a/tools/perf/Documentation/perf.txt
+++ b/tools/perf/Documentation/perf.txt
@@ -92,5 +92,5 @@ linkperf:perf-help[1], linkperf:perf-inject[1],
 linkperf:perf-intel-pt[1], linkperf:perf-iostat[1], linkperf:perf-kallsyms[1],
 linkperf:perf-kmem[1], linkperf:perf-kvm[1], linkperf:perf-lock[1],
 linkperf:perf-mem[1], linkperf:perf-probe[1], linkperf:perf-sched[1],
-linkperf:perf-script[1], linkperf:perf-test[1],
+linkperf:perf-script[1], linkperf:perf-sysinfo[1], linkperf:perf-test[1],
 linkperf:perf-trace[1], linkperf:perf-version[1]
-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v1 41/41] perf test: Add perf sysinfo shell test
  2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
                   ` (39 preceding siblings ...)
  2026-11-03 23:44 ` [PATCH v1 40/41] perf docs: Add perf-sysinfo man page Ian Rogers
@ 2026-11-03 23:44 ` Ian Rogers
  40 siblings, 0 replies; 42+ messages in thread
From: Ian Rogers @ 2026-11-03 23:44 UTC (permalink / raw)
  To: linux-perf-users, Arnaldo Carvalho de Melo, Namhyung Kim
  Cc: linux-kernel, Peter Zijlstra, Ingo Molnar, Jiri Olsa,
	Adrian Hunter, James Clark, Kan Liang, Dapeng Mi, Thomas Falcon,
	John Garry, Will Deacon, Leo Yan, Athira Rajeev, Thomas Richter,
	Ravi Bangoria, Sandipan Das, Ian Rogers

Add a shell test exercising default 'perf sysinfo' summary output,
'--pmus' per-PMU output, '-j' JSON validation, PMU glob filtering, and
'-i perf.data' inspection.

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

diff --git a/tools/perf/tests/shell/sysinfo.sh b/tools/perf/tests/shell/sysinfo.sh
new file mode 100755
index 000000000000..59091b4d750d
--- /dev/null
+++ b/tools/perf/tests/shell/sysinfo.sh
@@ -0,0 +1,83 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf sysinfo tests
+
+set -e
+
+shelldir=$(dirname "$0")
+. "${shelldir}"/lib/setup_python.sh
+
+sysinfo_output=""
+perf_data=""
+
+cleanup() {
+  rm -f "${sysinfo_output}" "${perf_data}" ${perf_data:+"${perf_data}.old"}
+  trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+  cleanup
+  exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+sysinfo_output=$(mktemp /tmp/__perf_test.sysinfo_output.XXXXX)
+perf_data=$(mktemp /tmp/__perf_test.sysinfo.perf.data.XXXXX)
+
+test_sysinfo_default() {
+  echo "Default perf sysinfo output test"
+  perf sysinfo -o "${sysinfo_output}"
+  grep -q "^Summary:" "${sysinfo_output}"
+  grep -q "System rating:" "${sysinfo_output}"
+  perf sysinfo --pmus -o "${sysinfo_output}"
+  grep -q "^software (" "${sysinfo_output}"
+  grep -q "^Summary:" "${sysinfo_output}"
+  echo "Default perf sysinfo output test [Success]"
+}
+
+test_sysinfo_json() {
+  echo "Json perf sysinfo output test"
+  perf sysinfo -j -o "${sysinfo_output}"
+  $PYTHON -m json.tool "${sysinfo_output}" /dev/null
+  echo "Json perf sysinfo output test [Success]"
+}
+
+test_sysinfo_filter() {
+  echo "Filtered perf sysinfo output test"
+  perf sysinfo -o "${sysinfo_output}" software
+  grep -q "^software (" "${sysinfo_output}"
+  if grep -q "Modes:.*sampling" "${sysinfo_output}"; then
+    grep -q "Sample types:.*ip" "${sysinfo_output}"
+    if grep -q "Sample types:.*regs_user" "${sysinfo_output}"; then
+      grep -q "User registers:" "${sysinfo_output}"
+    fi
+    if grep -q "Sample types:.*regs_intr" "${sysinfo_output}"; then
+      grep -q "Intr registers:" "${sysinfo_output}"
+    fi
+    grep -q "Features:.*leader_sampling" "${sysinfo_output}"
+    grep -q "Features:.*sw_grouping" "${sysinfo_output}"
+  fi
+  echo "Filtered perf sysinfo output test [Success]"
+}
+
+test_sysinfo_perf_data() {
+  echo "perf.data perf sysinfo test"
+  if ! perf record -e software/cpu-clock/u -o "${perf_data}" -- true 2>/dev/null; then
+    echo "perf.data perf sysinfo test [Skipped: perf record failed]"
+    return
+  fi
+  perf sysinfo -i "${perf_data}" -o "${sysinfo_output}"
+  grep -q "^Summary:" "${sysinfo_output}"
+  perf sysinfo --pmus -i "${perf_data}" -o "${sysinfo_output}"
+  grep -q "software" "${sysinfo_output}"
+  perf sysinfo -i "${perf_data}" -j -o "${sysinfo_output}"
+  $PYTHON -m json.tool "${sysinfo_output}" /dev/null
+  echo "perf.data perf sysinfo test [Success]"
+}
+
+test_sysinfo_default
+test_sysinfo_json
+test_sysinfo_filter
+test_sysinfo_perf_data
+cleanup
+exit 0
-- 
2.56.0.385.gac671c819a-goog


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

end of thread, other threads:[~2026-11-03 23:46 UTC | newest]

Thread overview: 42+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
2026-11-03 23:44 ` [PATCH v1 01/41] perf env: Make lazy perf_env initialization thread-safe Ian Rogers
2026-11-03 23:44 ` [PATCH v1 02/41] perf env: Add host_env() singleton Ian Rogers
2026-11-03 23:44 ` [PATCH v1 03/41] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 04/41] perf pmus: Introduce struct perf_pmus and perf_env__pmus() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 05/41] perf pmus: Pass struct perf_pmus to find helpers Ian Rogers
2026-11-03 23:44 ` [PATCH v1 06/41] perf pmus: Pass struct perf_pmus to scan helpers Ian Rogers
2026-11-03 23:44 ` [PATCH v1 07/41] perf pmu-events: Add e_machine to struct pmu_events_map Ian Rogers
2026-11-03 23:44 ` [PATCH v1 08/41] perf pmu-events: Pass e_machine to find_core_*_table() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 09/41] perf header: Pass e_machine to strcmp_cpuid_str() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 10/41] perf header: Support CPUID matching across all architectures Ian Rogers
2026-11-03 23:44 ` [PATCH v1 11/41] perf test: Make the CPUID matching test architecture independent Ian Rogers
2026-11-03 23:44 ` [PATCH v1 12/41] perf test: Add CPUID matching tests for other architectures Ian Rogers
2026-11-03 23:44 ` [PATCH v1 13/41] perf pmus: Add perf_pmus__read_from_env() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 14/41] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 15/41] perf metricgroup: Move matching of metrics to recorded events from perf script Ian Rogers
2026-11-03 23:44 ` [PATCH v1 16/41] perf parse-events: Decode metric-id terms when adding events Ian Rogers
2026-11-03 23:44 ` [PATCH v1 17/41] perf evsel: Generalize metric_id to a set of metric_ids Ian Rogers
2026-11-03 23:44 ` [PATCH v1 18/41] perf pmu: Add perf_pmu__for_each_name_from_config() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 19/41] perf metricgroup: Improve matching of metrics to recorded events Ian Rogers
2026-11-03 23:44 ` [PATCH v1 20/41] perf metricgroup: Add metricgroup__add_recorded_metrics() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 21/41] perf script: Fix metrics computed from samples Ian Rogers
2026-11-03 23:44 ` [PATCH v1 22/41] perf script: Don't drop metric samples for CPUs missing from the evsel Ian Rogers
2026-11-03 23:44 ` [PATCH v1 23/41] perf stat report: Display metrics for recorded events Ian Rogers
2026-11-03 23:44 ` [PATCH v1 24/41] perf script: Display metrics for perf stat record data Ian Rogers
2026-11-03 23:44 ` [PATCH v1 25/41] perf test: Add stat record metrics test Ian Rogers
2026-11-03 23:44 ` [PATCH v1 26/41] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers
2026-11-03 23:44 ` [PATCH v1 27/41] perf mem-events: Expose PMU init and name formatting helpers Ian Rogers
2026-11-03 23:44 ` [PATCH v1 28/41] perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling Ian Rogers
2026-11-03 23:44 ` [PATCH v1 29/41] perf pmu: Probe supported sample_type flags Ian Rogers
2026-11-03 23:44 ` [PATCH v1 30/41] perf pmu: Probe precise_ip levels Ian Rogers
2026-11-03 23:44 ` [PATCH v1 31/41] perf pmu: Probe privilege exclusion modifiers Ian Rogers
2026-11-03 23:44 ` [PATCH v1 32/41] perf pmu: Probe execution and AUX area features Ian Rogers
2026-11-03 23:44 ` [PATCH v1 33/41] perf pmu: Probe event group size limits Ian Rogers
2026-11-03 23:44 ` [PATCH v1 34/41] perf pmu: Probe user and interrupt sample register masks Ian Rogers
2026-11-03 23:44 ` [PATCH v1 35/41] perf pmu: Probe interrupt registers that require precise sampling Ian Rogers
2026-11-03 23:44 ` [PATCH v1 36/41] perf pmu: Probe memory profiling events and sample formats Ian Rogers
2026-11-03 23:44 ` [PATCH v1 37/41] perf pmu: Probe user-space mmap capabilities Ian Rogers
2026-11-03 23:44 ` [PATCH v1 38/41] perf pmu: Probe branch instruction skid Ian Rogers
2026-11-03 23:44 ` [PATCH v1 39/41] perf sysinfo: Add sysinfo subcommand Ian Rogers
2026-11-03 23:44 ` [PATCH v1 40/41] perf docs: Add perf-sysinfo man page Ian Rogers
2026-11-03 23:44 ` [PATCH v1 41/41] perf test: Add perf sysinfo shell test Ian Rogers

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®