mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH v1 0/2] perf sysinfo: Add subcommand to probe and summarize PMU capabilities
@ 2026-10-16 21:32 Ian Rogers
  2026-10-16 21:32 ` [RFC PATCH v1 1/2] perf pmus: Add PMU environment loading and live capability probing Ian Rogers
  2026-10-16 21:32 ` [RFC PATCH v1 2/2] perf sysinfo: Add sysinfo subcommand, documentation, and test Ian Rogers
  0 siblings, 2 replies; 3+ messages in thread
From: Ian Rogers @ 2026-10-16 21:32 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel, Stephane Eranian

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.

This RFC series introduces 'perf sysinfo' to inspect, actively probe,
and summarize the PMU and performance monitoring capabilities of a live
system or a recorded perf.data file:

- Patch 1 adds PMU environment reconstruction from perf_env headers
  (perf_pmus__read_from_env()), public duplicate-collapsing PMU scan
  helpers, and active live capability probing via perf_event_open()
  (perf_pmu__probe()). Probing tests counting, sampling, branch stack
  sampling, precise_ip levels (:p, :pp, :ppp) and empirically measured
  branch instruction skid across an 8,192-nop runway, supported
  perf_event_sample_format bits, user/intr general-purpose and SIMD
  register masks, memory profiling events, privilege exclusion
  modifiers, execution features (leader sampling, sw_grouping,
  auto-counter reload, AUX pause/resume/sample), user-space rdpmc
  availability/enablement and PMC bit-width, and maximum event group
  sizes (both openable and simultaneously active without multiplexing).

- Patch 2 adds the 'perf sysinfo' subcommand, documentation, and shell
  test. 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.

Example default 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 (76 sysfs, 3085 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, 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: 18 (active: 8)
      Max GP event group size: 16 (active: 7)

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

Ian Rogers (2):
  perf pmus: Add PMU environment loading and live capability probing
  perf sysinfo: Add sysinfo subcommand, documentation, and test

 tools/perf/Build                          |    1 +
 tools/perf/Documentation/perf-sysinfo.txt |  109 ++
 tools/perf/Documentation/perf.txt         |    2 +-
 tools/perf/builtin-help.c                 |    3 +-
 tools/perf/builtin-sysinfo.c              | 1958 ++++++++++++++++++++
 tools/perf/builtin.h                      |    1 +
 tools/perf/perf.c                         |    1 +
 tools/perf/tests/shell/sysinfo.sh         |   71 +
 tools/perf/util/mem-events.c              |   14 +-
 tools/perf/util/mem-events.h              |    3 +
 tools/perf/util/pmu.c                     | 1976 ++++++++++++++++++++-
 tools/perf/util/pmu.h                     |  135 +-
 tools/perf/util/pmus.c                    |  221 ++-
 tools/perf/util/pmus.h                    |    4 +
 14 files changed, 4486 insertions(+), 13 deletions(-)
 create mode 100644 tools/perf/Documentation/perf-sysinfo.txt
 create mode 100644 tools/perf/builtin-sysinfo.c
 create mode 100755 tools/perf/tests/shell/sysinfo.sh

-- 
2.56.0.212.g129ede20d9-goog


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

* [RFC PATCH v1 1/2] perf pmus: Add PMU environment loading and live capability probing
  2026-10-16 21:32 [RFC PATCH v1 0/2] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
@ 2026-10-16 21:32 ` Ian Rogers
  2026-10-16 21:32 ` [RFC PATCH v1 2/2] perf sysinfo: Add sysinfo subcommand, documentation, and test Ian Rogers
  1 sibling, 0 replies; 3+ messages in thread
From: Ian Rogers @ 2026-10-16 21:32 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel, Stephane Eranian

Add helpers to reconstruct PMU lists and capabilities from perf_env
headers (for perf.data files), expose duplicate uncore PMU scanning and
mem-events formatting helpers, and add perf_pmu__probe() to actively
probe live PMU capabilities via perf_event_open().

Probed capabilities stored in struct perf_pmu::probed include counting,
sampling, and branch_stack modes, precise_ip levels and measured branch
instruction skid, supported perf_event_sample_format flags, user and
interrupt sampling registers, memory profiling events and sample types,
privilege exclusion modifiers, leader sampling, software event grouping,
auto-counter reload, AUX actions (start_paused, pause, resume,
aux_sample), user-space counter reading (rdpmc) and PMC width, and
rotating event group size limits.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/mem-events.c |   14 +-
 tools/perf/util/mem-events.h |    3 +
 tools/perf/util/pmu.c        | 1976 +++++++++++++++++++++++++++++++++-
 tools/perf/util/pmu.h        |  135 ++-
 tools/perf/util/pmus.c       |  221 +++-
 tools/perf/util/pmus.h       |    4 +
 6 files changed, 2342 insertions(+), 11 deletions(-)

diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c
index 0cdcb47647bd..eb2a4973e385 100644
--- a/tools/perf/util/mem-events.c
+++ b/tools/perf/util/mem-events.c
@@ -79,12 +79,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];
@@ -193,13 +193,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->mem_events)
 		return -ENOENT;
 
 	for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
@@ -209,7 +209,7 @@ static int __perf_pmu__mem_events_init(struct perf_pmu *pmu)
 		 * If the event entry isn't valid, skip initialization
 		 * and "e->supported" will keep false.
 		 */
-		if (!e->tag)
+		if (!e || !e->tag)
 			continue;
 
 		e->supported |= perf_pmu__mem_events_supported(mnt, pmu, e);
@@ -225,7 +225,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..64517ca053d3 100644
--- a/tools/perf/util/mem-events.h
+++ b/tools/perf/util/mem-events.h
@@ -31,7 +31,10 @@ 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);
+int perf_pmu__mem_events_init_pmu(struct perf_pmu *pmu);
 int perf_pmu__mem_events_init(void);
+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);
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 01753d5c69d1..0d1c5ba7d426 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -38,7 +38,22 @@
 #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>
+#include <link.h>
 
 #define UNIT_MAX_LEN	31 /* max length for event unit name */
 
@@ -2139,7 +2154,7 @@ 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->sysfs_aliases_loaded ? 0 : tp_pmu__num_events(pmu);
 	if (perf_pmu__is_hwmon(pmu))
 		return hwmon_pmu__num_events(pmu);
 	if (perf_pmu__is_drm(pmu))
@@ -2275,7 +2290,7 @@ 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->sysfs_aliases_loaded ? 0 : tp_pmu__for_each_event(pmu, state, cb);
 	if (perf_pmu__is_hwmon(pmu))
 		return hwmon_pmu__for_each_event(pmu, state, cb);
 	if (perf_pmu__is_drm(pmu))
@@ -2564,6 +2579,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;
@@ -2798,9 +2844,1935 @@ 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_GROUP_SIZE 64
+#define MAX_PROBE_CANDIDATES 16
+
+static unsigned long dummy_bp_var;
+
+static bool get_self_uprobe_target(char *exe_path, __u64 *out_offset)
+{
+	ElfW(Ehdr) ehdr;
+	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 ||
+	    ehdr.e_phentsize < sizeof(ElfW(Phdr))) {
+		close(fd);
+		return false;
+	}
+
+	for (unsigned int i = 0; i < ehdr.e_phnum; i++) {
+		ElfW(Phdr) phdr;
+		off_t pos = (off_t)ehdr.e_phoff + (off_t)i * ehdr.e_phentsize;
+
+		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;
+}
+
+static int select_probe_cpu(const struct perf_pmu *pmu)
+{
+	struct perf_cpu_map *online = perf_cpu_map__new_online_cpus();
+	int cpu = 0;
+
+	if (pmu->cpus && !perf_cpu_map__is_empty(pmu->cpus)) {
+		int nr = perf_cpu_map__nr(pmu->cpus);
+
+		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)) {
+					cpu = c.cpu;
+					break;
+				}
+			}
+		}
+	} else if (online && !perf_cpu_map__is_empty(online)) {
+		cpu = perf_cpu_map__cpu(online, 0).cpu;
+	}
+
+	perf_cpu_map__put(online);
+	return cpu >= 0 ? cpu : 0;
+}
+
+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 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;
+}
+
+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_open, bool *open_capped,
+			       int *max_active, bool *active_capped)
+{
+	struct perf_event_attr leader_attr;
+	int leader_fd;
+	int fds[MAX_PROBE_GROUP_SIZE];
+	int nr_followers = 0;
+	bool has_time_read = true;
+	bool active_ok = true;
+	enum rlimit_action set_rlimit = NO_CHANGE;
+	cpu_set_t saved_affinity, probe_affinity;
+	bool affinity_changed = false;
+
+	*max_open = 0;
+	*open_capped = false;
+	*max_active = 0;
+	*active_capped = false;
+
+	if (!attrs || nr_attrs == 0)
+		return;
+
+	if (pid == 0 && cpu >= 0) {
+		CPU_ZERO(&saved_affinity);
+		if (sched_getaffinity(0, sizeof(saved_affinity), &saved_affinity) == 0) {
+			CPU_ZERO(&probe_affinity);
+			CPU_SET(cpu, &probe_affinity);
+			if (sched_setaffinity(0, sizeof(probe_affinity), &probe_affinity) == 0)
+				affinity_changed = true;
+		}
+	}
+
+	leader_attr = attrs[0];
+	leader_attr.disabled = 1;
+	leader_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
+
+	leader_fd = sys_perf_event_open(&leader_attr, pid, cpu, -1, flags);
+	if (leader_fd < 0) {
+		leader_attr.read_format = 0;
+		has_time_read = false;
+		leader_fd = sys_perf_event_open(&leader_attr, pid, cpu, -1, flags);
+	}
+	if (leader_fd < 0)
+		goto out_affinity;
+
+	*max_open = 1;
+	if (has_time_read && check_group_active(leader_fd, -1))
+		*max_active = 1;
+	else
+		active_ok = false;
+
+	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 && follower_attr.read_format != 0) {
+			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;
+		*max_open = count;
+		if (active_ok && has_time_read) {
+			if (check_group_active(leader_fd, fd))
+				*max_active = count;
+			else
+				active_ok = false;
+		}
+	}
+
+	if (*max_open >= MAX_PROBE_GROUP_SIZE)
+		*open_capped = true;
+	if (*max_active >= MAX_PROBE_GROUP_SIZE)
+		*active_capped = true;
+
+	for (int i = 0; i < nr_followers; i++)
+		close(fds[i]);
+	close(leader_fd);
+
+out_affinity:
+	if (affinity_changed)
+		sched_setaffinity(0, sizeof(saved_affinity), &saved_affinity);
+}
+
+enum probe_exclude_flag {
+	PROBE_EXCLUDE_USER,
+	PROBE_EXCLUDE_KERNEL,
+	PROBE_EXCLUDE_HV,
+	PROBE_EXCLUDE_IDLE,
+	PROBE_EXCLUDE_HOST,
+	PROBE_EXCLUDE_GUEST,
+};
+
+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;
+}
+
+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_AUX | PERF_SAMPLE_CGROUP |
+			PERF_SAMPLE_DATA_PAGE_SIZE | PERF_SAMPLE_CODE_PAGE_SIZE |
+			PERF_SAMPLE_WEIGHT_STRUCT;
+	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.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_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 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 registers outside the base GPR mask. On x86, check
+	 * APX EGPRs (R16-R31, bits 24..39) and SSP (bit 40) with
+	 * sample_simd_regs_enabled=1 if the host supports SIMD ABI; otherwise
+	 * check legacy PEBS XMM registers (bits 32..63) which require
+	 * precise_ip > 0 on interrupt register sampling.
+	 */
+	if (EM_HOST == EM_X86_64 || EM_HOST == EM_386) {
+		int host_abi = 0;
+		u64 host_mask = intr ? perf_intr_reg_mask(EM_HOST, &host_abi) :
+				       perf_user_reg_mask(EM_HOST, &host_abi);
+
+		if ((host_abi & PERF_SAMPLE_REGS_ABI_SIMD) && (host_mask & ~gpr_mask)) {
+			u64 egpr_masks[] = { GENMASK_ULL(39, 24), BIT_ULL(40) };
+
+			for (size_t i = 0; i < ARRAY_SIZE(egpr_masks); i++) {
+				struct perf_event_attr e_attr = *base_attr;
+
+				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 = egpr_masks[i];
+				else
+					e_attr.sample_regs_user = egpr_masks[i];
+
+				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 |= egpr_masks[i];
+					abi |= PERF_SAMPLE_REGS_ABI_SIMD;
+				}
+			}
+		}
+	}
+
+	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);
+				}
+			}
+		}
+	}
+
+	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);
+			}
+		}
+
+		if (pred)
+			*out_pred_mask = supported_classes;
+		else
+			*out_simd_mask = supported_classes;
+	}
+
+	*out_mask = mask;
+	*out_abi = abi;
+}
+
+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;
+}
+
+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);
+	}
+}
+
+static void probe_mmap_caps(struct perf_pmu *pmu,
+			    const struct perf_event_attr *base_attr,
+			    int pmu_cpu, 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);
+	cpu_set_t old_mask, mask;
+	bool affinity_changed = false;
+	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);
+	}
+
+	if (pmu_cpu >= 0 && pmu_cpu < CPU_SETSIZE &&
+	    sched_getaffinity(0, sizeof(old_mask), &old_mask) == 0) {
+		CPU_ZERO(&mask);
+		CPU_SET(pmu_cpu, &mask);
+		if (sched_setaffinity(0, sizeof(mask), &mask) == 0)
+			affinity_changed = true;
+	}
+
+	attr.disabled = 0;
+
+	/*
+	 * On architectures such as arm64, user-space counter reading must be
+	 * explicitly requested via format/rdpmc (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;
+
+		r_attr.config1 |= 0x2;
+		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);
+	}
+
+	if (affinity_changed)
+		sched_setaffinity(0, sizeof(old_mask), &old_mask);
+}
+
+static bool run_skid_branch_loop(u64 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 8192\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 = 8192;
+	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 1000\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 = 1000;
+	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
+}
+
+static void probe_pmu_skid(struct perf_pmu *pmu,
+			   const struct perf_event_attr *base_attr,
+			   int pmu_cpu, 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;
+	cpu_set_t old_mask, mask;
+	bool affinity_changed = false;
+
+	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__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;
+
+	if (pmu_cpu >= 0 && pmu_cpu < CPU_SETSIZE &&
+	    sched_getaffinity(0, sizeof(old_mask), &old_mask) == 0) {
+		CPU_ZERO(&mask);
+		CPU_SET(pmu_cpu, &mask);
+		if (sched_setaffinity(0, sizeof(mask), &mask) == 0)
+			affinity_changed = true;
+	}
+
+	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);
+	}
+
+	if (affinity_changed)
+		sched_setaffinity(0, sizeof(old_mask), &old_mask);
+}
+
+void perf_pmu__probe(struct perf_pmu *pmu)
+{
+	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;
+	int pmu_cpu;
+	pid_t pid = 0;
+	int cpu = 0;
+	int last_err = 0;
+	bool found = false;
+
+	if (!pmu || pmu->probed.checked)
+		return;
+	if (perf_pmu__kind(pmu) != PERF_PMU_KIND_PE)
+		return;
+
+	pmu->probed.checked = true;
+	pmu_cpu = select_probe_cpu(pmu);
+
+	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) {
+		if (pmu->selectable) {
+			char buf[256];
+
+			if (perf_pmu__has_format(pmu, "swfilt")) {
+				snprintf(buf, sizeof(buf), "%s/swfilt=1/", pmu->name);
+				probe_candidates__add(&cands, buf);
+			}
+			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__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;
+		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);
+	}
+
+	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 */
+	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;
+	}
+
+	/* 2. All enum perf_event_sample_format types */
+	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;
+				}
+			}
+		}
+
+		probe_pmu_skid(pmu, &base_attr, pmu_cpu, flags);
+	}
+
+	/* 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);
+	}
+
+	/* 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);
+	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);
+
+	/* 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;
+		}
+	}
+
+	/* 5b. User-space mmap capabilities (rdpmc, user_time, pmc_width) */
+	probe_mmap_caps(pmu, &base_attr, pmu_cpu, 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;
+		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;
+		l_attr.config2 = 0x2;
+
+		f_attr.disabled = 0;
+		f_attr.freq = 0;
+		f_attr.sample_period = 100000;
+		f_attr.sample_type = PERF_SAMPLE_IP;
+		f_attr.config2 = 0x3;
+
+		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_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);
+		}
+	}
+
+	/* 6. Max event group size & max simultaneously active group size */
+	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;
+			pmu->probed.max_active_group_size = MAX_PROBE_GROUP_SIZE;
+			pmu->probed.max_active_capped = true;
+		} else {
+			pmu->probed.max_group_size = 1;
+			pmu->probed.max_active_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,
+				   &pmu->probed.max_active_group_size,
+				   &pmu->probed.max_active_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",
+			"ref-cycles",
+		};
+		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,
+			PERF_COUNT_HW_REF_CPU_CYCLES,
+		};
+		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.config = gp_configs[i];
+				if (perf_pmus__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,
+					   &pmu->probed.max_active_gp_group_size,
+					   &pmu->probed.max_active_gp_capped);
+		}
+	}
+
+	if (keepalive_fd >= 0)
+		close(keepalive_fd);
+}
+
 const char *perf_pmu__name_from_config(struct perf_pmu *pmu, u64 config)
 {
 	struct hashmap_entry *entry;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 2c8916ad073e..fe8a90752396 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -207,6 +207,138 @@ 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 {
+		/** @checked: Has perf_pmu__probe() been run on this PMU? */
+		bool checked;
+		/** @open_supported: Could a probe event be opened on this PMU? */
+		bool open_supported;
+		/** @can_count: Supports counting (sample_period == 0). */
+		bool can_count;
+		/** @can_sample: Supports interrupt-based sampling (sample_period > 0). */
+		bool can_sample;
+		/** @can_branch_stack: Supports PERF_SAMPLE_BRANCH_STACK. */
+		bool can_branch_stack;
+		/** @can_leader_sampling: Supports group leader sampling (:S). */
+		bool can_leader_sampling;
+		/** @can_sw_grouping: Supports grouping software events with this PMU's events. */
+		bool can_sw_grouping;
+		/** @can_inherit: Supports perf_event_attr::inherit across child tasks. */
+		bool can_inherit;
+		/** @can_freq: Supports dynamic sample frequency (perf_event_attr::freq). */
+		bool can_freq;
+		/** @can_write_backward: Supports overwrite ring buffer (write_backward). */
+		bool can_write_backward;
+		/** @can_callchain: Supports PERF_SAMPLE_CALLCHAIN. */
+		bool can_callchain;
+		/** @can_data_src: Supports PERF_SAMPLE_DATA_SRC. */
+		bool can_data_src;
+		/** @can_weight: Supports PERF_SAMPLE_WEIGHT. */
+		bool can_weight;
+		/** @can_pinned: Supports perf_event_attr::pinned. */
+		bool can_pinned;
+		/** @can_exclusive: Supports perf_event_attr::exclusive. */
+		bool can_exclusive;
+		/** @can_format_lost: Supports PERF_FORMAT_LOST. */
+		bool can_format_lost;
+		/** @can_acr: Supports auto-counter reload (acr_mask / ratio-to-prev). */
+		bool can_acr;
+		/** @can_aux_start_paused: Supports aux_start_paused (PERF_PMU_CAP_AUX_PAUSE). */
+		bool can_aux_start_paused;
+		/** @can_aux_pause: Supports aux_pause action on group followers. */
+		bool can_aux_pause;
+		/** @can_aux_resume: Supports aux_resume action on group followers. */
+		bool can_aux_resume;
+		/** @can_aux_sample: Supports AUX area snapshot sampling (aux_sample_size). */
+		bool can_aux_sample;
+		/** @rdpmc_available: PMU supports user-space counter reading (rdpmc/mrs/csrr). */
+		bool rdpmc_available;
+		/** @can_user_rdpmc: User-space counter reading enabled (cap_user_rdpmc & index). */
+		bool can_user_rdpmc;
+		/** @rdpmc_works: User-space counter read instruction executed and worked. */
+		bool rdpmc_works;
+		/** @can_user_time: Supports user-space time conversion (cap_user_time). */
+		bool can_user_time;
+		/** @can_user_time_zero: Supports cap_user_time_zero in mmap page. */
+		bool can_user_time_zero;
+		/** @can_user_time_short: Supports cap_user_time_short in mmap page. */
+		bool can_user_time_short;
+		/** @exclude_user: Supports perf_event_attr::exclude_user. */
+		bool exclude_user;
+		/** @exclude_kernel: Supports perf_event_attr::exclude_kernel. */
+		bool exclude_kernel;
+		/** @exclude_hv: Supports perf_event_attr::exclude_hv. */
+		bool exclude_hv;
+		/** @exclude_idle: Supports perf_event_attr::exclude_idle. */
+		bool exclude_idle;
+		/** @exclude_host: Supports perf_event_attr::exclude_host. */
+		bool exclude_host;
+		/** @exclude_guest: Supports perf_event_attr::exclude_guest. */
+		bool exclude_guest;
+		/** @max_group_capped: Was max_group_size capped at probe limit? */
+		bool max_group_capped;
+		/** @max_active_capped: Was max_active_group_size capped at probe limit? */
+		bool max_active_capped;
+		/** @max_gp_group_capped: Was max_gp_group_size capped at probe limit? */
+		bool max_gp_group_capped;
+		/** @max_active_gp_capped: Was max_active_gp_group_size capped at probe limit? */
+		bool max_active_gp_capped;
+		/** @probe_event: Name of the event used to probe the PMU. */
+		char *probe_event;
+		/** @open_err: errno if open_supported is false. */
+		int open_err;
+		/** @max_precise: Maximum precise_ip level (0..3) supported. */
+		int max_precise;
+		/** @precise_mask: Bitmask of supported precise_ip levels (1..3). */
+		unsigned int precise_mask;
+		/** @max_group_size: Max events in a group accepted by perf_event_open(). */
+		int max_group_size;
+		/** @max_active_group_size: Max events in a group active simultaneously. */
+		int max_active_group_size;
+		/** @max_gp_group_size: Max GP events in a group accepted by perf_event_open(). */
+		int max_gp_group_size;
+		/** @max_active_gp_group_size: Max GP events in a group active simultaneously. */
+		int max_active_gp_group_size;
+		/** @user_regs_abi: ABI flags for user_regs_mask. */
+		int user_regs_abi;
+		/** @intr_regs_abi: ABI flags for intr_regs_mask. */
+		int intr_regs_abi;
+		/** @rdpmc_sysfs: Value from sysfs rdpmc or perf_user_access (-1 if unknown). */
+		int rdpmc_sysfs;
+		/** @mem_events_mask: Bitmask of memory event types verified by probing. */
+		unsigned int mem_events_mask;
+		/** @pmc_width: Bit-width of hardware counter for user-space rdpmc. */
+		u16 pmc_width;
+		/** @sample_types: Bitmask of supported enum perf_event_sample_format flags. */
+		u64 sample_types;
+		/** @mem_sample_types: Bitmask of memory sample formats verified on mem_events. */
+		u64 mem_sample_types;
+		/** @user_regs_mask: Bitmask of supported sample_regs_user registers. */
+		u64 user_regs_mask;
+		/** @intr_regs_mask: Bitmask of supported sample_regs_intr registers. */
+		u64 intr_regs_mask;
+		/** @user_simd_mask: Bitmask of supported user SIMD vector register classes. */
+		u64 user_simd_mask;
+		/** @user_pred_mask: Bitmask of supported user SIMD predicate register classes. */
+		u64 user_pred_mask;
+		/** @intr_simd_mask: Bitmask of supported intr SIMD vector register classes. */
+		u64 intr_simd_mask;
+		/** @intr_pred_mask: Bitmask of supported intr SIMD predicate register classes. */
+		u64 intr_pred_mask;
+		/** @skid: Measured branch instruction skid at precise_ip levels (0..3). */
+		struct {
+			bool measured;
+			int samples;
+			int exact_samples;
+			int min_skid;
+			int max_skid;
+			double avg_skid;
+		} skid[4];
+	} probed;
 };
 
 struct perf_pmu_info {
@@ -329,8 +461,9 @@ 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);
-
+int perf_pmu__add_cap(struct perf_pmu *pmu, const char *name, const char *value);
 int perf_pmu__caps_parse(struct perf_pmu *pmu);
+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,
diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
index 1355b317f3ed..44f24e3a177a 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -7,13 +7,16 @@
 #include <subcmd/pager.h>
 #include <sys/types.h>
 #include <ctype.h>
+#include <errno.h>
 #include <pthread.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include "cpumap.h"
 #include "debug.h"
 #include "drm_pmu.h"
+#include "env.h"
 #include "evsel.h"
 #include "pmus.h"
 #include "pmu.h"
@@ -452,7 +455,7 @@ struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const c
 	return NULL;
 }
 
-static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu)
+struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu)
 {
 	bool use_core_pmus = !pmu || pmu->is_core;
 	int last_pmu_name_len = 0;
@@ -489,6 +492,27 @@ static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu)
 	return NULL;
 }
 
+size_t perf_pmu__num_duplicates(const struct perf_pmu *pmu)
+{
+	const struct perf_pmu *pos = pmu;
+	size_t base_len;
+	size_t count = 1;
+
+	if (!pmu || pmu->is_core)
+		return 1;
+
+	base_len = pmu_name_len_no_suffix(pmu->name ?: "");
+	list_for_each_entry_continue(pos, &other_pmus, 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_pmu *pmu, const char *compat)
 {
 	if (!pmu) {
@@ -916,3 +940,198 @@ struct perf_pmu *perf_pmus__fake_pmu(void)
 
 	return &fake;
 }
+
+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") || 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(NULL)))
+		return is_pmu_core(name);
+
+	return false;
+}
+
+static struct perf_pmu *env_pmu_find_or_add(const char *name, __u32 type,
+					    struct perf_env *env,
+					    const struct pmu_events_table *core_table)
+{
+	struct perf_pmu *pmu = pmu_find(name);
+
+	if (pmu) {
+		if (pmu->type == PERF_PMU_TYPE_FAKE && type != PERF_PMU_TYPE_FAKE)
+			pmu->type = type;
+		return pmu;
+	}
+
+	pmu = zalloc(sizeof(*pmu));
+	if (!pmu)
+		return NULL;
+
+	if (perf_pmu__init(pmu, type, name) != 0) {
+		perf_pmu__delete(pmu);
+		return NULL;
+	}
+
+	pmu->sysfs_aliases_loaded = true;
+	pmu->caps_initialized = true;
+	pmu->max_precise = -1;
+	pmu->is_core = env_is_pmu_core(name, env);
+	pmu->is_uncore = strstarts(name, "uncore_");
+
+	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 ? &core_pmus : &other_pmus);
+	return pmu;
+}
+
+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_env *env)
+{
+	const struct pmu_events_table *core_table = NULL;
+	struct perf_pmu *pmu;
+
+	if (!env)
+		return -EINVAL;
+
+	perf_pmus__destroy();
+	read_pmu_types = PERF_TOOL_PMU_TYPE_ALL_MASK;
+
+	if (env->cpuid) {
+		char cpuid_str[128];
+		char vendor[32];
+		unsigned int family, model, step;
+		const char *cpuid = env->cpuid;
+
+		/*
+		 * x86 HEADER_CPUID stores "vendor,family,model,step" in decimal
+		 * while pmu-events tables match "vendor-family-HEXMODEL-HEXSTEP".
+		 */
+		if (sscanf(env->cpuid, "%31[^,],%u,%u,%u",
+			   vendor, &family, &model, &step) == 4) {
+			snprintf(cpuid_str, sizeof(cpuid_str), "%s-%u-%X-%X",
+				 vendor, family, model, step);
+			cpuid = cpuid_str;
+		}
+		core_table = find_core_events_table(perf_env__arch(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(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(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, &core_pmus);
+		}
+		if (hn->cpus && !pmu->cpus)
+			pmu->cpus = perf_cpu_map__new(hn->cpus);
+	}
+
+	/*
+	 * 3. Populate/enrich from HEADER_CPU_PMU_CAPS.
+	 * On hybrid systems HEADER_CPU_PMU_CAPS duplicates the first core PMU
+	 * while HEADER_PMU_CAPS has the per-PMU capabilities, so only create a
+	 * "cpu" PMU entry if no core PMUs exist yet.
+	 */
+	if (env->nr_cpu_pmu_caps > 0 && env->cpu_pmu_caps) {
+		pmu = pmu_find("cpu");
+		if (!pmu && list_empty(&core_pmus)) {
+			pmu = env_pmu_find_or_add("cpu", PERF_TYPE_RAW,
+						  env, core_table);
+		}
+		if (pmu)
+			env_pmu_add_caps_strings(pmu, env->nr_cpu_pmu_caps, env->cpu_pmu_caps);
+	}
+
+	/* 4. 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(pc->pmu_name, PERF_PMU_TYPE_FAKE,
+					  env, core_table);
+		if (pmu)
+			env_pmu_add_caps_strings(pmu, pc->nr_caps, pc->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, &core_pmus, list) {
+			if (!pmu->cpus)
+				pmu->cpus = perf_cpu_map__new(buf);
+		}
+	}
+
+	list_sort(NULL, &core_pmus, pmus_cmp);
+	list_sort(NULL, &other_pmus, pmus_cmp);
+	return 0;
+}
diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h
index 0d55edb3f2fc..f1402e50b5b9 100644
--- a/tools/perf/util/pmus.h
+++ b/tools/perf/util/pmus.h
@@ -6,6 +6,7 @@
 #include <stddef.h>
 
 struct perf_event_attr;
+struct perf_env;
 struct perf_pmu;
 struct print_callbacks;
 
@@ -14,16 +15,19 @@ size_t pmu_name_len_no_suffix(const char *str);
 int pmu_name_cmp(const char *lhs_pmu_name, const char *rhs_pmu_name);
 
 void perf_pmus__destroy(void);
+int perf_pmus__read_from_env(struct perf_env *env);
 
 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);
 
 struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu);
+struct perf_pmu *perf_pmus__scan_skip_duplicates(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);
+size_t perf_pmu__num_duplicates(const struct perf_pmu *pmu);
 
 const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(const char *str);
 
-- 
2.56.0.212.g129ede20d9-goog


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

* [RFC PATCH v1 2/2] perf sysinfo: Add sysinfo subcommand, documentation, and test
  2026-10-16 21:32 [RFC PATCH v1 0/2] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
  2026-10-16 21:32 ` [RFC PATCH v1 1/2] perf pmus: Add PMU environment loading and live capability probing Ian Rogers
@ 2026-10-16 21:32 ` Ian Rogers
  1 sibling, 0 replies; 3+ messages in thread
From: Ian Rogers @ 2026-10-16 21:32 UTC (permalink / raw)
  To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
	Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
  Cc: linux-perf-users, linux-kernel, Stephane Eranian

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.

For each PMU, 'perf sysinfo' reports static attributes (type,
core/uncore/software/auxtrace/drm/hwmon/tool classification, CPUs,
capabilities, sysfs formats, event counts, and memory profiling events)
as well as probed runtime capabilities in text or JSON format, followed
by a system-wide summary of core features, memory and PCIe/IO bus
bandwidth monitoring, total events and metrics, pipeline metric
drill-down levels, and an overall system rating.

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/Build                          |    1 +
 tools/perf/Documentation/perf-sysinfo.txt |  109 ++
 tools/perf/Documentation/perf.txt         |    2 +-
 tools/perf/builtin-help.c                 |    3 +-
 tools/perf/builtin-sysinfo.c              | 1958 +++++++++++++++++++++
 tools/perf/builtin.h                      |    1 +
 tools/perf/perf.c                         |    1 +
 tools/perf/tests/shell/sysinfo.sh         |   71 +
 8 files changed, 2144 insertions(+), 2 deletions(-)
 create mode 100644 tools/perf/Documentation/perf-sysinfo.txt
 create mode 100644 tools/perf/builtin-sysinfo.c
 create mode 100755 tools/perf/tests/shell/sysinfo.sh

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/Documentation/perf-sysinfo.txt b/tools/perf/Documentation/perf-sysinfo.txt
new file mode 100644
index 000000000000..4fc5fe229060
--- /dev/null
+++ b/tools/perf/Documentation/perf-sysinfo.txt
@@ -0,0 +1,109 @@
+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'
+- 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 event group size accepted by 'perf_event_open' and maximum
+  simultaneously active counters in a group without multiplexing
+
+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]
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..8bc13f3dedb0
--- /dev/null
+++ b/tools/perf/builtin-sysinfo.c
@@ -0,0 +1,1958 @@
+// 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 "builtin.h"
+
+#include "util/color.h"
+#include "util/cpumap.h"
+#include "util/data.h"
+#include "util/debug.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"
+
+#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/pager.h>
+#include <subcmd/parse-options.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.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_matches_filters(const struct perf_pmu *pmu,
+				int argc, const char **argv)
+{
+	if (argc <= 0)
+		return true;
+
+	for (int i = 0; i < argc; i++) {
+		const char *pat = argv[i];
+
+		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 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 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;
+	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}");
+
+	total_events = perf_pmu__num_events((struct perf_pmu *)pmu);
+	if (total_events > 0) {
+		size_t sysfs_events = pmu->sysfs_aliases;
+		size_t json_events = total_events > sysfs_events ? total_events - sysfs_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);
+
+			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");
+			fprintf(fp, ",\n\t\t\"MaxActiveGroupSize\": %d",
+				p->max_active_group_size);
+			fprintf(fp, ",\n\t\t\"MaxActiveGroupSizeCapped\": %s",
+				p->max_active_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\"MaxActiveGpGroupSize\": %d",
+					p->max_active_gp_group_size);
+			}
+		} 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;
+	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);
+
+	total_events = perf_pmu__num_events((struct perf_pmu *)pmu);
+	if (total_events > 0) {
+		size_t sysfs_events = pmu->sysfs_aliases;
+		size_t json_events = total_events > sysfs_events ? total_events - sysfs_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_abi,
+					  p->intr_simd_mask, p->intr_pred_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 (active: %s%d)\n",
+				p->max_group_capped ? ">=" : "",
+				p->max_group_size,
+				p->max_active_capped ? ">=" : "",
+				p->max_active_group_size);
+			if (p->max_gp_group_size > 0 &&
+			    (p->max_gp_group_size != p->max_group_size ||
+			     p->max_active_gp_group_size != p->max_active_group_size)) {
+				fprintf(fp, "    Max GP event group size: %s%d (active: %s%d)\n",
+					p->max_gp_group_capped ? ">=" : "",
+					p->max_gp_group_size,
+					p->max_active_gp_capped ? ">=" : "",
+					p->max_active_gp_group_size);
+			}
+		}
+	}
+}
+
+static int process_header_feature(const struct perf_tool *tool,
+				  struct perf_session *session,
+				  union perf_event *event)
+{
+	int ret = perf_event__process_feature(tool, session, event);
+
+	if (ret == 0 && event->header.size == sizeof(struct perf_record_header_feature) &&
+	    (int)event->feat.feat_id >= session->header.last_feat) {
+		session_done = 1;
+	}
+	return ret;
+}
+
+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)
+{
+	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_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(pmu)) != NULL) {
+		size_t pmu_events = perf_pmu__num_events(pmu);
+		size_t sysfs_ev = pmu->sysfs_aliases;
+		size_t json_ev = pmu_events > sysfs_ev ? pmu_events - sysfs_ev : 0;
+
+		total_pmus++;
+		total_events += pmu_events;
+		total_sysfs_events += sysfs_ev;
+		total_json_events += json_ev;
+
+		if (pmu->is_core) {
+			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 && pmu->probed.open_supported) {
+				if (pmu->probed.can_sample)
+					core_can_sample = true;
+				if (pmu->probed.max_precise > 0) {
+					core_precise = true;
+					if (pmu->probed.max_precise > max_precise)
+						max_precise = pmu->probed.max_precise;
+				}
+				if (pmu->probed.can_user_rdpmc)
+					core_rdpmc = true;
+				if (pmu->probed.can_branch_stack)
+					core_branch_stack = true;
+				if (pmu->probed.mem_events_mask)
+					core_mem_events = true;
+				if (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;
+				if (pmu->mem_events)
+					core_mem_events = true;
+			}
+		} else if (pmu->type == PERF_TYPE_SOFTWARE) {
+			if (!probed || !pmu->probed.checked || pmu->probed.can_sample)
+				has_sw_sampling = true;
+		}
+
+		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 && session->header.env.arch && session->header.env.cpuid)
+		mtable = find_core_metrics_table(session->header.env.arch,
+						 session->header.env.cpuid);
+	else
+		mtable = pmu_metrics_table__find();
+
+	metricgroup__for_each_metric(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 (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 && (!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_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;
+		}
+	} else {
+		setup_pager();
+	}
+
+	if (input_name) {
+		data.path = input_name;
+		data.force = force;
+
+		perf_tool__init(&tool, /*ordered_events=*/false);
+		tool.attr = perf_event__process_attr;
+		tool.feature = process_header_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);
+		if (ret) {
+			pr_err("Failed to read PMU information from %s: %s\n",
+			       input_name, strerror(-ret));
+			goto out_session;
+		}
+	}
+
+	if (json)
+		fputs("[", fp);
+
+	while ((pmu = show_all ? perf_pmus__scan(pmu) :
+				 perf_pmus__scan_skip_duplicates(pmu)) != NULL) {
+		if (!pmu_matches_filters(pmu, argc, argv))
+			continue;
+
+		if (do_probe && !input_name &&
+		    (show_pmus || pmu->is_core || 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 d60bc929c257..a6fab1e6eb1c 100644
--- a/tools/perf/perf.c
+++ b/tools/perf/perf.c
@@ -112,6 +112,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 },
diff --git a/tools/perf/tests/shell/sysinfo.sh b/tools/perf/tests/shell/sysinfo.sh
new file mode 100755
index 000000000000..40e90af6dca2
--- /dev/null
+++ b/tools/perf/tests/shell/sysinfo.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf sysinfo tests
+
+set -e
+
+shelldir=$(dirname "$0")
+. "${shelldir}"/lib/setup_python.sh
+
+sysinfo_output=$(mktemp /tmp/__perf_test.sysinfo_output.XXXXX)
+perf_data=$(mktemp /tmp/__perf_test.sysinfo.perf.data.XXXXX)
+
+cleanup() {
+  rm -f "${sysinfo_output}" "${perf_data}" "${perf_data}.old"
+  trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+  cleanup
+  exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+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}"
+  grep -q "Sample types:.*ip" "${sysinfo_output}"
+  grep -q "User registers:" "${sysinfo_output}"
+  grep -q "Intr registers:" "${sysinfo_output}"
+  grep -q "Features:.*leader_sampling" "${sysinfo_output}"
+  grep -q "Features:.*sw_grouping" "${sysinfo_output}"
+  echo "Filtered perf sysinfo output test [Success]"
+}
+
+test_sysinfo_perf_data() {
+  echo "perf.data perf sysinfo test"
+  perf record -e software/cpu-clock/u -o "${perf_data}" -- true 2>/dev/null
+  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.212.g129ede20d9-goog


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

end of thread, other threads:[~2026-10-16 21:33 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-16 21:32 [RFC PATCH v1 0/2] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
2026-10-16 21:32 ` [RFC PATCH v1 1/2] perf pmus: Add PMU environment loading and live capability probing Ian Rogers
2026-10-16 21:32 ` [RFC PATCH v1 2/2] perf sysinfo: Add sysinfo subcommand, documentation, and 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®