* [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities
@ 2026-11-13 19:55 Ian Rogers
2026-11-13 19:55 ` [PATCH v2 01/16] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers
` (15 more replies)
0 siblings, 16 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
Cc: linux-perf-users, linux-kernel
This series adds the 'perf sysinfo' subcommand to probe and summarize
the PMU capabilities of a system, or of the system a perf.data file was
recorded on.
It depends on, and applies on top of, the series "perf: Per-env PMUs,
metric event sharing, and recorded event metrics":
https://lore.kernel.org/all/20261113195335.2865725-1-irogers@google.com/
Together they were sent as the single 41 patch v1 series "perf: Per-env
PMUs, recorded event metrics, and perf sysinfo":
https://lore.kernel.org/all/20261103234502.1008382-1-irogers@google.com/
which followed on from the v2 RFC series ("perf sysinfo: Add subcommand
to probe and summarize PMU capabilities"):
https://lore.kernel.org/linux-perf-users/20261017185442.2137623-1-irogers@google.com/
Determining what performance monitoring hardware, driver capabilities,
and event/metric support are available on a system currently requires
piecing together sysfs files under /sys/bus/event_source/devices/,
perf list output, and trial-and-error perf_event_open() calls. Across
architectures and SoCs, support varies widely: an advanced system
provides core PMUs with zero-skid precise event sampling
(PEBS/IBS/SPE), hardware branch stack recording (LBR/BRBE),
user-space counter reading (rdpmc), memory profiling (DATA_SRC),
hardware AUX trace PMUs, memory and PCIe/IO interconnect bandwidth
PMUs, and multi-level pipeline metric drill-down hierarchies, whereas
a minimal system may only offer software timer sampling.
- Patch 1 exports perf_pmus__scan_skip_duplicates() and adds
perf_pmu__num_duplicates() to count numbered uncore PMU instances.
- Patch 2 exports perf_pmu__mem_events_init_pmu() and
perf_pmu__mem_events_name() for per-PMU memory profiling events.
- Patches 3-13 add struct perf_pmu::probed and perf_pmu__probe(),
split into individual capability probes:
- Patch 3: Basic event opening, counting, interrupt sampling, and
branch stack sampling (PERF_SAMPLE_BRANCH_STACK).
- Patch 4: Supported perf_event_sample_format flags (sample_type).
- Patch 5: Supported precise_ip levels (:p, :pp, :ppp).
- Patch 6: Privilege and execution-mode exclusion modifiers
(exclude_user, exclude_kernel, exclude_hv, exclude_idle,
exclude_host, exclude_guest).
- Patch 7: Execution and AUX area features (leader_sampling,
sw_grouping, auto-counter reload, aux_output, aux_pause,
aux_sample, inherit, sample_freq, write_backward, callchain,
data_src, weight, pinned, exclusive, format_lost, cgroup).
- Patch 8: Event group size limits (max events counted
simultaneously without multiplexing, for both all core counters
and general-purpose counters only). The number of events accepted
by perf_event_open() isn't reported as PMUs may accept groups that
can never be scheduled.
- Patch 9: User and interrupt sample register bitmasks
(general-purpose and SIMD vector/predicate registers).
- Patch 10: Interrupt registers that can only be sampled with
precise sampling, such as x86 XMM registers captured by PEBS.
- Patch 11: Memory profiling events (ldlat-loads, ldlat-stores,
load-store) and supported memory sample formats.
- Patch 12: User-space perf_event_mmap_page capabilities
(cap_user_rdpmc, cap_user_time*, pmc_width, and live rdpmc
verification).
- Patch 13: Empirically measured branch instruction skid across
precise_ip levels 0..3 using a noinline nop-runway loop.
- Patch 14 adds the 'perf sysinfo' subcommand. By default,
'perf sysinfo' outputs a colorized system capability summary,
pipeline metric drill-down hierarchy (via <metric>_group chains),
overall system rating (Advanced, Standard, Basic, Minimal), and any
missing baseline features. Passing '-p'/'--pmus' (or a PMU glob
filter, '-v'/'--verbose', '-a'/'--all', or '-j'/'--json') displays
detailed per-PMU static and probed attributes.
- Patch 15 adds the 'perf-sysinfo' man page documentation.
- Patch 16 adds the 'perf sysinfo' shell test.
Changes since v1
================
- Split from the v1 series into this series, which depends on the
"perf: Per-env PMUs, metric event sharing, and recorded event
metrics" series.
- Patch 1's perf_pmu__num_duplicates() reports a duplicate count of 1
for core PMUs, as core PMUs are no longer skipped as duplicates.
- Rebased onto the latest perf-tools-next.
- Addressed Sashiko review feedback:
- Probing: skip ftrace tracepoints and limit tracepoint candidates,
don't try an all zero config for tracepoint, breakpoint, kprobe and
uprobe PMUs, report tracefs errors, treat precise_ip as ignored by
sampling-only PMUs where all levels open (AMD IBS), probe inherit
with a task event, probe sample_freq with a low frequency, add
perf_reg_probe_candidates() for architecture specific extended
register candidates (x86 eGPRs/SSP and XMM), require rdpmc counts
to advance and read kernel/perf_user_access for core PMUs.
- 'perf sysinfo -j' IntrRegisters excludes precise only registers,
matching the text output.
- Only check register names in the sysinfo test on x86_64 and
aarch64.
Changes since v2 RFC
====================
- Split the two perf_pmu__probe() commits into 10 per-capability commits
(Patches 3-13) and moved the 'perf sysinfo' commits to the end of the
series (Patches 14-16).
- The per-perf_env PMU support used by 'perf sysinfo -i' is in the
series this one depends on.
Example outputs
===============
Example default 'perf sysinfo' output on an Intel AlderLake hybrid system:
$ perf sysinfo
Summary:
Core PMUs (2, hybrid): cpu_atom, cpu_core
Core features:
Interrupt-based sampling: supported
Precise event sampling: supported (max_precise: 3 [3 - SAMPLE_IP must have 0 skid, or uses randomization])
Branch stack sampling (LBR/BRBE): supported (depth: 32 branches)
User-space counter reading (rdpmc): supported
Leader sampling: supported
Memory profiling events (DATA_SRC): supported
Hardware AUX trace PMUs: intel_bts, intel_pt
Bus & uncore monitoring:
Memory bandwidth PMUs: uncore_imc, uncore_imc_free_running
PCIe / IO interconnect bandwidth PMUs: uncore_arb
Events & metrics:
Available events: 3161 total (120 sysfs, 3041 json) across 28 PMUs
Available metrics: 438 metrics in 192 groups (advanced)
Metric drill-down levels (6): L1 (TopdownL1: 7 metrics), L2 (TopdownL2: 14 metrics), L3 (TopdownL3: 44 metrics), L4 (TopdownL4: 45 metrics), L5 (TopdownL5: 15 metrics), L6 (TopdownL6: 9 metrics)
System rating: Advanced (core PMU with precise sampling, branch stack (LBR), user rdpmc, bus PMUs, and metric drill-down)
Example filtered per-PMU output:
$ perf sysinfo cpu_core
cpu_core (type: 4 [PERF_TYPE_RAW], core):
CPUs: 0-15 (16 CPUs)
Capabilities: branches=32, max_precise=3, pmu_name=alderlake_hybrid
Formats: event (config:0-7), pc (config:19), edge (config:18), offcore_rsp (config1:0-63), ldlat (config1:0-15), inv (config:23), umask (config:8-15), frontend (config1:0-23), cmask (config:24-31)
Events: 1563 total (20 sysfs, 1543 json)
Memory events: ldlat-loads ({cpu_core/mem-loads-aux/,cpu_core/mem-loads,ldlat=30/}:P), ldlat-stores (cpu_core/mem-stores/P)
Probed capabilities (event: cpu_core/cycles/):
Modes: counting, sampling, branch_stack
Precise sampling: :p (1 - SAMPLE_IP must have constant skid), :pp (2 - SAMPLE_IP requested to have 0 skid), :ppp (3 - SAMPLE_IP must have 0 skid, or uses randomization) (max_precise: 3)
Measured branch skid (instructions from branch):
level 0 (no :p): min 105, max 174, avg 156.8 (0/32 exact_ip)
level 1 (:p) : min 1, max 1, avg 1.0 (0/31 exact_ip)
level 2 (:pp) : min 0, max 0, avg 0.0 (31/31 exact_ip)
level 3 (:ppp) : min 0, max 0, avg 0.0 (32/32 exact_ip)
Sample types: ip, tid, time, addr, read, callchain, id, cpu, period, stream_id, raw, branch_stack, regs_user, stack_user, weight, data_src, identifier, transaction, regs_intr, aux, cgroup, data_page_size, code_page_size, weight_struct
User registers: AX, BX, CX, DX, SI, DI, BP, SP, IP, FLAGS, CS, SS, R8, R9, R10, R11, R12, R13, R14, R15
Intr registers: AX, BX, CX, DX, SI, DI, BP, SP, IP, FLAGS, CS, SS, R8, R9, R10, R11, R12, R13, R14, R15
Intr registers (precise sampling only): XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15
Memory events (probed): ldlat-loads, ldlat-stores (sample types: addr, weight, data_src, transaction, data_page_size, weight_struct)
Exclusion modifiers: exclude_user, exclude_kernel, exclude_hv, exclude_idle, exclude_host, exclude_guest
Features: leader_sampling, sw_grouping, inherit, sample_freq, write_backward, callchain, data_src, weight, pinned, exclusive, format_lost, user_rdpmc, user_time, user_time_zero
User space counter reading (rdpmc): available, enabled, working (width: 48 bits)
Max event group size: 8
Max GP event group size: 7
Example output on a Raspberry Pi 5 (ARM Cortex-A76):
$ sudo perf sysinfo armv8_cortex_a76
armv8_cortex_a76 (type: 9 [dynamic], core):
CPUs: 0-3 (4 CPUs)
Capabilities: slots=0x00000000, threshold_max=0x00000000, bus_slots=0x00000000, bus_width=0x00000000
Formats: threshold_count (config1:2), event (config:0-15), long (config1:0), rdpmc (config1:1), threshold (config1:5-16), threshold_compare (config1:3-4)
Events: 1336 total (40 sysfs, 1296 json)
Probed capabilities (event: armv8_cortex_a76/cycles/):
Modes: counting, sampling
Precise sampling: not supported (0 - SAMPLE_IP can have arbitrary skid)
Measured branch skid (instructions from branch):
level 0 (no :p): min 340, max 352, avg 346.2 (0/31 exact_ip)
Sample types: ip, tid, time, addr, read, callchain, id, cpu, period, stream_id, raw, regs_user, stack_user, weight, data_src, identifier, transaction, regs_intr, phys_addr, aux, cgroup, data_page_size, code_page_size, weight_struct
User registers: x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, lr, sp, pc
Intr registers: x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, lr, sp, pc
Exclusion modifiers: exclude_user, exclude_kernel, exclude_hv, exclude_host, exclude_guest
Features: leader_sampling, sw_grouping, inherit, sample_freq, write_backward, callchain, data_src, weight, pinned, exclusive, format_lost, user_time, user_time_zero, user_time_short
User space counter reading (rdpmc): available, disabled (sysfs: 0) (width: 32 bits)
Max event group size: 7
Max GP event group size: 6
$ sudo perf sysinfo
Summary:
Core PMUs (1): armv8_cortex_a76
Core features:
Interrupt-based sampling: supported
Precise event sampling: missing (0 - SAMPLE_IP can have arbitrary skid)
Branch stack sampling (LBR/BRBE): missing
User-space counter reading (rdpmc): missing/disabled
Leader sampling: supported
Memory profiling events (DATA_SRC): missing
Hardware AUX trace PMUs: missing
Bus & uncore monitoring:
Memory bandwidth PMUs: missing
PCIe / IO interconnect bandwidth PMUs: missing
Events & metrics:
Available events: 3695 total (2371 sysfs, 1324 json) across 11 PMUs
Available metrics: 21 metrics in 5 groups (minimal)
Metric drill-down levels: none (flat metrics only)
System rating: Basic (core PMU with basic counting/sampling only; missing advanced features)
Missing baseline features: PCIe/IO interconnect bandwidth PMUs, hardware AUX trace PMU, hardware branch stack sampling (LBR/BRBE), memory bus bandwidth PMUs, memory load/store profiling events, multi-level pipeline drill-down metrics (<metric>_group), precise event sampling (PEBS/IBS/SPE), user-space counter reading (rdpmc)
This patch series was generated with the assistance of antigravity and
Gemini 4.0.
Ian Rogers (16):
perf pmus: Expose duplicate uncore PMU scanning helpers
perf mem-events: Expose PMU init and name formatting helpers
perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling
perf pmu: Probe supported sample_type flags
perf pmu: Probe precise_ip levels
perf pmu: Probe privilege exclusion modifiers
perf pmu: Probe execution and AUX area features
perf pmu: Probe event group size limits
perf pmu: Probe user and interrupt sample register masks
perf pmu: Probe interrupt registers that require precise sampling
perf pmu: Probe memory profiling events and sample formats
perf pmu: Probe user-space mmap capabilities
perf pmu: Probe branch instruction skid
perf sysinfo: Add sysinfo subcommand
perf docs: Add perf-sysinfo man page
perf test: Add perf sysinfo shell test
tools/perf/Build | 1 +
tools/perf/Documentation/perf-sysinfo.txt | 113 +
tools/perf/Documentation/perf.txt | 2 +-
tools/perf/builtin-help.c | 3 +-
tools/perf/builtin-sysinfo.c | 2024 ++++++++++++++++
tools/perf/builtin.h | 1 +
tools/perf/perf.c | 1 +
tools/perf/tests/shell/sysinfo.sh | 88 +
tools/perf/util/mem-events.c | 12 +-
tools/perf/util/mem-events.h | 20 +
.../perf/util/perf-regs-arch/perf_regs_x86.c | 10 +
tools/perf/util/perf_regs.c | 16 +
tools/perf/util/perf_regs.h | 2 +
tools/perf/util/pmu.c | 2145 +++++++++++++++++
tools/perf/util/pmu.h | 197 +-
tools/perf/util/pmus.c | 26 +-
tools/perf/util/pmus.h | 21 +
17 files changed, 4672 insertions(+), 10 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
base-commit: 4af65363b11fc4fe65fcb497f68bbe4f3e3a4d2c
prerequisite-patch-id: acc4e1da262cddf12ae8d1c74892fcebe33dc93f
prerequisite-patch-id: fa6ab66e2aa7716cbd182de0f92456f6b6edb60d
prerequisite-patch-id: fa99754455a4f9e681661837fb6d6835b7d425c5
prerequisite-patch-id: 492d797b54751da228948fac5f40f663f5ea3c62
prerequisite-patch-id: b99319e27324e7083fc6cd1ecd5aec042c562b53
prerequisite-patch-id: 696e5fff8cf5a4ce80bd1ed889d50bdaa78dc392
prerequisite-patch-id: ef1abbc7686bcb7785a16a90f1154260973c5f5e
prerequisite-patch-id: 27a18ce42c011a70a0464458dc50e67c75469e96
prerequisite-patch-id: 81ce66bcf2bf46a082a6660f32cc72fd22aa81c6
prerequisite-patch-id: 983a33d705bc24f9388e7949b1b8749a3864d736
prerequisite-patch-id: 2862db041ea93e0288a0a00f6f89485c16ed614e
prerequisite-patch-id: f1e7c89e852518a146a71eb66777eef402a6a0e0
prerequisite-patch-id: 0de8efa65dfeae2eafc0a5d73ef8886c789d6c6f
prerequisite-patch-id: 0dbd6a8a6e1d5da3c66ae81e965e6b959c2616f6
prerequisite-patch-id: 867e4c865f782d64c6c0f5a56b663d48c146c9a5
prerequisite-patch-id: b8f9e457fbc0e73d0d22a61d9019dfa72eed9169
prerequisite-patch-id: b07eae786bb7c5c8e4463bf1ebeac4751a99f5c8
prerequisite-patch-id: 1ec4d183f7e2cd4d8a677acec4bfec0bf8c48b07
prerequisite-patch-id: f831001f8da976c05c831a2233bd54879caef69c
prerequisite-patch-id: d1909df2b6cc3b5ed8fa59babde6867d17b76a2c
prerequisite-patch-id: 5668910716a6b85387440573e4ef9bbe0c84c1e9
prerequisite-patch-id: 54c6ff1d396ee78b279041a5be55c70ce8b908f1
prerequisite-patch-id: ef24f3acdcf55f5bc48ce3c7b40bdab0e75da477
prerequisite-patch-id: eae037d7c72f03e535eb9e3ffcb87d843592912b
prerequisite-patch-id: 44da8d15a6114a3205dc7fb18a84f1983593113a
prerequisite-patch-id: e665d48c6980205eeb2f51c8af9108cd30eacabb
prerequisite-patch-id: 37c850ea0c093556c4902e79a6698d4101ff837c
prerequisite-patch-id: 8f91bf53dd07618c46f435d925156bd8c11abb32
prerequisite-patch-id: d28eb7fb9c332c1dfe064afe794b4669af0647ee
prerequisite-patch-id: 815eff9d52cab3857b4e8d07c82fbf486af3b860
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 01/16] perf pmus: Expose duplicate uncore PMU scanning helpers
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 02/16] perf mem-events: Expose PMU init and name formatting helpers Ian Rogers
` (14 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Export perf_pmus__scan_skip_duplicates() and add
perf_pmu__num_duplicates() to count numbered PMU instances sharing a
common prefix (such as uncore_imc_0..uncore_imc_7) so subcommands like
'perf sysinfo' can collapse duplicate uncore PMUs by default and report
their instance count.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmus.c | 26 +++++++++++++++++++++++++-
tools/perf/util/pmus.h | 21 +++++++++++++++++++++
2 files changed, 46 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
index b924ff6180e4..931a129bb45b 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -485,7 +485,7 @@ struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmus *pmus,
return NULL;
}
-static struct perf_pmu *
+struct perf_pmu *
perf_pmus__scan_skip_duplicates(struct perf_pmus *pmus, struct perf_pmu *pmu)
{
bool use_core_pmus = !pmu || pmu->is_core;
@@ -522,6 +522,30 @@ perf_pmus__scan_skip_duplicates(struct perf_pmus *pmus, struct perf_pmu *pmu)
return NULL;
}
+size_t perf_pmu__num_duplicates(const struct perf_pmu *pmu)
+{
+ const struct list_head *head;
+ const struct perf_pmu *pos = pmu;
+ size_t base_len;
+ size_t count = 1;
+
+ /* Core PMUs are never duplicates, see perf_pmus__scan_skip_duplicates(). */
+ if (!pmu || !pmu->pmus || pmu->is_core)
+ return 1;
+
+ head = &pmu->pmus->other_pmus;
+ base_len = pmu_name_len_no_suffix(pmu->name ?: "");
+ list_for_each_entry_continue(pos, head, list) {
+ size_t pos_len = pmu_name_len_no_suffix(pos->name ?: "");
+
+ if (pos_len != base_len ||
+ strncmp(pmu->name ?: "", pos->name ?: "", base_len))
+ break;
+ count++;
+ }
+ return count;
+}
+
struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmus *pmus,
struct perf_pmu *pmu, const char *compat)
{
diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h
index 2b784cf648f3..44b5dc2fd36c 100644
--- a/tools/perf/util/pmus.h
+++ b/tools/perf/util/pmus.h
@@ -134,6 +134,26 @@ struct perf_pmu *perf_pmus__scan(struct perf_pmus *pmus, struct perf_pmu *pmu);
*/
struct perf_pmu *perf_pmus__scan_core(struct perf_pmus *pmus, struct perf_pmu *pmu);
+/**
+ * perf_pmus__scan_skip_duplicates() - Iterate over PMUs in @pmus, skipping
+ * numbered duplicate instances of
+ * non-core PMUs.
+ * @pmus: The PMU collection to scan.
+ * @pmu: Previous PMU returned, or NULL to start from the first PMU.
+ *
+ * Return: Next distinct prefix struct perf_pmu, or NULL when done.
+ */
+struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmus *pmus, struct perf_pmu *pmu);
+
+/**
+ * perf_pmu__num_duplicates() - Count consecutive PMUs sharing @pmu's prefix.
+ * @pmu: Representative PMU returned by perf_pmus__scan_skip_duplicates().
+ *
+ * Return: Number of PMU instances (including @pmu) sharing its base name,
+ * always 1 for core PMUs.
+ */
+size_t perf_pmu__num_duplicates(const struct perf_pmu *pmu);
+
/**
* perf_pmus__scan_for_event() - Iterate over PMUs in @pmus that have @event.
* @pmus: The PMU collection to scan.
@@ -260,6 +280,7 @@ struct perf_pmu *perf_pmus__add_test_hwmon_pmu(struct perf_pmus *pmus,
* Return: Pointer to the fake struct perf_pmu.
*/
struct perf_pmu *perf_pmus__fake_pmu(void);
+
/**
* perf_pmus__find_core_pmu() - Return the first core PMU in @pmus.
* @pmus: The PMU collection to search.
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 02/16] perf mem-events: Expose PMU init and name formatting helpers
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
2026-11-13 19:55 ` [PATCH v2 01/16] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 03/16] perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling Ian Rogers
` (13 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Export perf_pmu__mem_events_init_pmu() and perf_pmu__mem_events_name()
so callers can initialize and format per-PMU memory profiling events.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/mem-events.c | 12 ++++++------
tools/perf/util/mem-events.h | 20 ++++++++++++++++++++
2 files changed, 26 insertions(+), 6 deletions(-)
diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c
index 62543df0dca1..932efcfd82f7 100644
--- a/tools/perf/util/mem-events.c
+++ b/tools/perf/util/mem-events.c
@@ -82,12 +82,12 @@ int perf_pmu__mem_events_num_mem_pmus(struct perf_pmu *pmu)
return num;
}
-static const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i,
- char *buf, size_t buf_size)
+const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i,
+ char *buf, size_t buf_size)
{
struct perf_mem_event *e;
- if (i >= PERF_MEM_EVENTS__MAX || !pmu)
+ if (i >= PERF_MEM_EVENTS__MAX || !pmu || !pmu->mem_events)
return NULL;
e = &pmu->mem_events[i];
@@ -196,13 +196,13 @@ static bool perf_pmu__mem_events_supported(const char *mnt, struct perf_pmu *pmu
return !stat(path, &st);
}
-static int __perf_pmu__mem_events_init(struct perf_pmu *pmu)
+int perf_pmu__mem_events_init_pmu(struct perf_pmu *pmu)
{
const char *mnt = sysfs__mount();
bool found = false;
int j;
- if (!mnt)
+ if (!mnt || !pmu || pmu->from_env || !pmu->mem_events)
return -ENOENT;
for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
@@ -228,7 +228,7 @@ int perf_pmu__mem_events_init(void)
struct perf_pmu *pmu = NULL;
while ((pmu = perf_pmus__scan_mem(pmu)) != NULL) {
- if (__perf_pmu__mem_events_init(pmu))
+ if (perf_pmu__mem_events_init_pmu(pmu))
return -ENOENT;
}
diff --git a/tools/perf/util/mem-events.h b/tools/perf/util/mem-events.h
index 38b14c03392c..f2e622a048c8 100644
--- a/tools/perf/util/mem-events.h
+++ b/tools/perf/util/mem-events.h
@@ -31,8 +31,28 @@ extern struct perf_mem_event perf_mem_events[PERF_MEM_EVENTS__MAX];
extern bool perf_mem_record[PERF_MEM_EVENTS__MAX];
int perf_pmu__mem_events_parse(struct perf_pmu *pmu, const char *str);
+
+/**
+ * perf_pmu__mem_events_init_pmu() - Initialize memory event table for @pmu.
+ * @pmu: The PMU to check for memory event support.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int perf_pmu__mem_events_init_pmu(struct perf_pmu *pmu);
int perf_pmu__mem_events_init(void);
+/**
+ * perf_pmu__mem_events_name() - Format the event string for a memory event.
+ * @pmu: The PMU providing the memory event.
+ * @i: Memory event index (PERF_MEM_EVENTS__LOAD, etc.).
+ * @buf: Output buffer to format the event string into.
+ * @buf_size: Size of @buf in bytes.
+ *
+ * Return: Pointer to @buf on success, or NULL if invalid/unsupported.
+ */
+const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i,
+ char *buf, size_t buf_size);
+
struct perf_mem_event *perf_pmu__mem_events_ptr(struct perf_pmu *pmu, int i);
struct perf_pmu *perf_mem_events_find_pmu(void);
int perf_pmu__mem_events_num_mem_pmus(struct perf_pmu *pmu);
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 03/16] perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
2026-11-13 19:55 ` [PATCH v2 01/16] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers
2026-11-13 19:55 ` [PATCH v2 02/16] perf mem-events: Expose PMU init and name formatting helpers Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 04/16] perf pmu: Probe supported sample_type flags Ian Rogers
` (12 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Sysfs PMU metadata does not expose many runtime kernel and hardware
capabilities, such as whether a PMU supports counting vs interrupt-based
sampling or whether perf_event_open() succeeds under the current
privilege level.
Add struct perf_pmu::probed and perf_pmu__probe() to actively probe a
live PMU via perf_event_open() on a dedicated worker thread pinned to an
online CPU associated with the PMU. Select a representative probe event
for tracepoint, kprobe, uprobe, core, software, breakpoint, and uncore
PMUs, hold a keepalive fd for tracepoints to avoid repeated RCU
synchronize_rcu() delays on close, and record whether opening, counting
(sample_period == 0), and sampling (sample_period > 0) are supported.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 577 ++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/pmu.h | 31 ++-
2 files changed, 607 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 6db7d7df9c26..be93ff8b9948 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -4,12 +4,18 @@
#include <linux/string.h>
#include <linux/zalloc.h>
#include <linux/ctype.h>
+#include <linux/hw_breakpoint.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
+#include <elf.h>
#include <errno.h>
+#include <limits.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -20,6 +26,8 @@
#include <locale.h>
#include <fnmatch.h>
#include <math.h>
+#include <pthread.h>
+#include <sched.h>
#include "debug.h"
#include "evsel.h"
#include "pmu.h"
@@ -40,6 +48,14 @@
#include "strbuf.h"
#include "fncache.h"
#include "util/evsel_config.h"
+#include "cloexec.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 <regex.h>
#define UNIT_MAX_LEN 31 /* max length for event unit name */
@@ -2840,9 +2856,570 @@ void perf_pmu__delete(struct perf_pmu *pmu)
zfree(&pmu->name);
zfree(&pmu->alias_name);
zfree(&pmu->id);
+ zfree(&pmu->probed.probe_event);
free(pmu);
}
+#define MAX_PROBE_CANDIDATES 16
+#define MAX_TRACEPOINT_PROBE_CANDIDATES 3
+
+static unsigned long dummy_bp_var;
+
+/*
+ * Resolve /proc/self/exe and compute the file offset of its ELF entry point
+ * so the uprobe PMU can be probed against the running perf binary.
+ */
+static bool get_self_uprobe_target(char *exe_path, __u64 *out_offset)
+{
+#if __SIZEOF_POINTER__ == 8
+ Elf64_Ehdr ehdr;
+#else
+ Elf32_Ehdr ehdr;
+#endif
+ int fd;
+ bool found = false;
+
+ if (!realpath("/proc/self/exe", exe_path))
+ return false;
+
+ fd = open(exe_path, O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ return false;
+
+ if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr) ||
+ memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) {
+ close(fd);
+ return false;
+ }
+
+ for (unsigned int i = 0; i < ehdr.e_phnum; i++) {
+#if __SIZEOF_POINTER__ == 8
+ Elf64_Phdr phdr;
+#else
+ Elf32_Phdr phdr;
+#endif
+ off_t pos = (off_t)ehdr.e_phoff + (off_t)i * ehdr.e_phentsize;
+
+ if (ehdr.e_phentsize < sizeof(phdr))
+ break;
+
+ if (pread(fd, &phdr, sizeof(phdr), pos) != sizeof(phdr))
+ break;
+ if (phdr.p_type != PT_LOAD || !(phdr.p_flags & PF_X))
+ continue;
+ if (ehdr.e_entry >= phdr.p_vaddr &&
+ ehdr.e_entry < phdr.p_vaddr + phdr.p_filesz) {
+ *out_offset = phdr.p_offset + (ehdr.e_entry - phdr.p_vaddr);
+ found = true;
+ break;
+ }
+ }
+
+ close(fd);
+ return found;
+}
+
+struct probe_candidates {
+ char *names[MAX_PROBE_CANDIDATES];
+ size_t nr;
+};
+
+static void probe_candidates__add(struct probe_candidates *cands, const char *str)
+{
+ if (cands->nr >= MAX_PROBE_CANDIDATES || !str)
+ return;
+ for (size_t i = 0; i < cands->nr; i++) {
+ if (!strcmp(cands->names[i], str))
+ return;
+ }
+ cands->names[cands->nr] = strdup(str);
+ if (cands->names[cands->nr])
+ cands->nr++;
+}
+
+static void probe_candidates__exit(struct probe_candidates *cands)
+{
+ for (size_t i = 0; i < cands->nr; i++)
+ zfree(&cands->names[i]);
+ cands->nr = 0;
+}
+
+static int collect_probe_candidate_cb(void *vstate, struct pmu_event_info *info)
+{
+ struct probe_candidates *cands = vstate;
+ char buf[512];
+
+ if (info->deprecated)
+ return 0;
+ if (info->str && strstr(info->str, "=?"))
+ return 0;
+ if (!info->name)
+ return 0;
+
+ if (info->pmu && info->pmu->type == PERF_TYPE_TRACEPOINT) {
+ /* ftrace internal tracepoints, like ftrace:function, can't be opened. */
+ if (strstarts(info->name, "ftrace:"))
+ return 0;
+ probe_candidates__add(cands, info->name);
+ /* Opening tracepoints is slow, try only a few. */
+ return cands->nr >= MAX_TRACEPOINT_PROBE_CANDIDATES ? 1 : 0;
+ } else if (strchr(info->name, '/')) {
+ probe_candidates__add(cands, info->name);
+ } else if (info->pmu && info->pmu->name) {
+ snprintf(buf, sizeof(buf), "%s/%s/", info->pmu->name, info->name);
+ probe_candidates__add(cands, buf);
+ } else {
+ probe_candidates__add(cands, info->name);
+ }
+
+ return cands->nr >= MAX_PROBE_CANDIDATES ? 1 : 0;
+}
+
+static bool parse_candidate_attr(const char *event_str, struct perf_event_attr *attr)
+{
+ struct evlist *evlist;
+ struct parse_events_error err;
+ struct evsel *evsel;
+ int ret;
+
+ evlist = evlist__new();
+ if (!evlist)
+ return false;
+
+ parse_events_error__init(&err);
+ ret = __parse_events(evlist, event_str, /*pmu_filter=*/NULL,
+ /*cputype_filter=*/false, &err, /*fake_pmu=*/false,
+ /*warn_if_reordered=*/false, /*fake_tp=*/false);
+ parse_events_error__exit(&err);
+ if (ret || evlist__empty(evlist)) {
+ evlist__put(evlist);
+ return false;
+ }
+
+ evsel = evlist__first(evlist);
+ *attr = evsel->core.attr;
+ evlist__put(evlist);
+ return true;
+}
+
+/*
+ * Pick an online CPU associated with @pmu (preferring one in the caller's
+ * CPU affinity mask) on which to run perf_event_open() probes.
+ */
+static int select_probe_cpu(const struct perf_pmu *pmu)
+{
+ struct perf_cpu_map *online = perf_cpu_map__new_online_cpus();
+ cpu_set_t aff;
+ bool have_aff;
+ int cpu = 0;
+
+ CPU_ZERO(&aff);
+ have_aff = sched_getaffinity(0, sizeof(aff), &aff) == 0;
+
+ if (pmu->cpus && !perf_cpu_map__is_empty(pmu->cpus)) {
+ int nr = perf_cpu_map__nr(pmu->cpus);
+ bool matched_online = false;
+
+ cpu = perf_cpu_map__cpu(pmu->cpus, 0).cpu;
+ if (online) {
+ for (int i = 0; i < nr; i++) {
+ struct perf_cpu c = perf_cpu_map__cpu(pmu->cpus, i);
+
+ if (!perf_cpu_map__has(online, c))
+ continue;
+ if (!matched_online) {
+ cpu = c.cpu;
+ matched_online = true;
+ }
+ if (have_aff && c.cpu >= 0 && c.cpu < CPU_SETSIZE &&
+ CPU_ISSET(c.cpu, &aff)) {
+ cpu = c.cpu;
+ break;
+ }
+ }
+ }
+ } else if (online && !perf_cpu_map__is_empty(online)) {
+ int nr = perf_cpu_map__nr(online);
+
+ cpu = perf_cpu_map__cpu(online, 0).cpu;
+ if (have_aff) {
+ for (int i = 0; i < nr; i++) {
+ struct perf_cpu c = perf_cpu_map__cpu(online, i);
+
+ if (c.cpu >= 0 && c.cpu < CPU_SETSIZE &&
+ CPU_ISSET(c.cpu, &aff)) {
+ cpu = c.cpu;
+ break;
+ }
+ }
+ }
+ }
+
+ perf_cpu_map__put(online);
+ return cpu >= 0 ? cpu : 0;
+}
+
+/*
+ * Try opening @attr across task/CPU target modes, counting/sampling modes,
+ * and privilege exclusion combinations until a working baseline configuration
+ * is found.
+ */
+static bool try_open_attr_variants(struct perf_event_attr *attr,
+ bool is_uncore, int pmu_cpu,
+ unsigned long flags,
+ pid_t *out_pid, int *out_cpu,
+ int *last_err)
+{
+ pid_t pids[2];
+ size_t nr_pids = 2;
+
+ if (is_uncore) {
+ pids[0] = -1;
+ pids[1] = 0;
+ } else {
+ pids[0] = 0;
+ pids[1] = -1;
+ }
+
+ for (size_t p = 0; p < nr_pids; p++) {
+ pid_t pid = pids[p];
+
+ for (int samp = 0; samp < 2; samp++) {
+ for (int priv = 0; priv < 4; priv++) {
+ struct perf_event_attr try_attr = *attr;
+ int fd;
+
+ try_attr.size = sizeof(try_attr);
+ try_attr.disabled = 1;
+ try_attr.read_format = 0;
+ if (samp == 0) {
+ try_attr.sample_period = 0;
+ try_attr.freq = 0;
+ try_attr.sample_type = 0;
+ } else {
+ try_attr.sample_period = 10000;
+ try_attr.freq = 0;
+ try_attr.sample_type = PERF_SAMPLE_IP;
+ }
+
+ if (priv >= 1)
+ try_attr.exclude_kernel = 1;
+ if (priv >= 2)
+ try_attr.exclude_hv = 1;
+ if (priv >= 3)
+ try_attr.exclude_guest = 1;
+
+ fd = sys_perf_event_open(&try_attr, pid, pmu_cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ *attr = try_attr;
+ *out_pid = pid;
+ *out_cpu = pmu_cpu;
+ return true;
+ }
+ if (*last_err == 0 || *last_err == ENOENT || *last_err == EINVAL)
+ *last_err = errno;
+ }
+ }
+ }
+ return false;
+}
+
+static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
+{
+ struct probe_candidates cands = { .nr = 0 };
+ struct perf_event_attr base_attr;
+ char uprobe_exe_path[PATH_MAX];
+ __u64 uprobe_offset = 0;
+ unsigned long flags = perf_event_open_cloexec_flag();
+ int keepalive_fd = -1;
+ pid_t pid = 0;
+ int cpu = 0;
+ int last_err = 0;
+ bool found = false;
+
+ if (pmu->type == PERF_TYPE_TRACEPOINT) {
+ static const struct {
+ const char *sys;
+ const char *name;
+ } fast_tps[] = {
+ { "sched", "sched_switch" },
+ { "raw_syscalls", "sys_enter" },
+ { "irq", "irq_handler_entry" },
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(fast_tps); i++) {
+ int tp_id = tp_pmu__id(fast_tps[i].sys, fast_tps[i].name);
+
+ if (tp_id < 0) {
+ /* Record errors like EACCES reading tracefs. */
+ if (last_err == 0)
+ last_err = errno ?: ENOENT;
+ } else {
+ memset(&base_attr, 0, sizeof(base_attr));
+ base_attr.type = PERF_TYPE_TRACEPOINT;
+ base_attr.config = tp_id;
+ if (try_open_attr_variants(&base_attr, false, pmu_cpu,
+ flags, &pid, &cpu, &last_err)) {
+ char tp_buf[128];
+
+ snprintf(tp_buf, sizeof(tp_buf), "%s:%s",
+ fast_tps[i].sys, fast_tps[i].name);
+ pmu->probed.probe_event = strdup(tp_buf);
+ found = true;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false,
+ &cands, collect_probe_candidate_cb);
+ }
+ } else if (pmu->name && !strcmp(pmu->name, "kprobe")) {
+ static const char * const kprobe_funcs[] = {
+ "vfs_read",
+ "schedule",
+ "do_sys_openat2",
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(kprobe_funcs); i++) {
+ memset(&base_attr, 0, sizeof(base_attr));
+ base_attr.type = pmu->type;
+ base_attr.size = sizeof(base_attr);
+ base_attr.kprobe_func = (uintptr_t)kprobe_funcs[i];
+ base_attr.probe_offset = 0;
+ if (try_open_attr_variants(&base_attr, false, pmu_cpu,
+ flags, &pid, &cpu, &last_err)) {
+ char kp_buf[128];
+
+ snprintf(kp_buf, sizeof(kp_buf), "kprobe:%s",
+ kprobe_funcs[i]);
+ pmu->probed.probe_event = strdup(kp_buf);
+ found = true;
+ break;
+ }
+ }
+ } else if (pmu->name && !strcmp(pmu->name, "uprobe")) {
+ if (get_self_uprobe_target(uprobe_exe_path, &uprobe_offset)) {
+ memset(&base_attr, 0, sizeof(base_attr));
+ base_attr.type = pmu->type;
+ base_attr.size = sizeof(base_attr);
+ base_attr.uprobe_path = (uintptr_t)uprobe_exe_path;
+ base_attr.probe_offset = uprobe_offset;
+ if (try_open_attr_variants(&base_attr, false, pmu_cpu,
+ flags, &pid, &cpu, &last_err)) {
+ const char *slash = strrchr(uprobe_exe_path, '/');
+ const char *bname = slash ? slash + 1 : uprobe_exe_path;
+
+ if (asprintf(&pmu->probed.probe_event, "uprobe:%s+0x%llx",
+ bname, (unsigned long long)uprobe_offset) < 0)
+ pmu->probed.probe_event = NULL;
+ found = true;
+ }
+ }
+ } else if (pmu->is_core) {
+ static const char * const core_events[] = {
+ "cycles",
+ "instructions",
+ "branches",
+ "branch-misses",
+ "cache-misses",
+ "cache-references",
+ "bus-cycles",
+ "ref-cycles",
+ };
+ char buf[256];
+
+ for (size_t i = 0; i < ARRAY_SIZE(core_events); i++) {
+ snprintf(buf, sizeof(buf), "%s/%s/", pmu->name, core_events[i]);
+ probe_candidates__add(&cands, buf);
+ }
+ perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false,
+ &cands, collect_probe_candidate_cb);
+ } else if (pmu->type == PERF_TYPE_SOFTWARE) {
+ static const char * const sw_events[] = {
+ "software/cpu-clock/",
+ "software/task-clock/",
+ "software/page-faults/",
+ "software/context-switches/",
+ "software/cpu-migrations/",
+ "software/minor-faults/",
+ "software/major-faults/",
+ "software/dummy/",
+ };
+
+ for (size_t i = 0; i < ARRAY_SIZE(sw_events); i++)
+ probe_candidates__add(&cands, sw_events[i]);
+ } else if (pmu->type != PERF_TYPE_BREAKPOINT) {
+ char buf[256];
+
+ /*
+ * PMUs like AMD IBS without hardware privilege filtering need
+ * swfilt=1 to open with exclude_kernel, as needed when probing
+ * unprivileged. They aren't selectable and have no sysfs events.
+ */
+ if (perf_pmu__has_format(pmu, "swfilt")) {
+ snprintf(buf, sizeof(buf), "%s/swfilt=1/", pmu->name);
+ probe_candidates__add(&cands, buf);
+ }
+ if (pmu->selectable) {
+ snprintf(buf, sizeof(buf), "%s//", pmu->name);
+ probe_candidates__add(&cands, buf);
+ }
+ perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false,
+ &cands, collect_probe_candidate_cb);
+ }
+
+ for (size_t i = 0; !found && i < cands.nr; i++) {
+ memset(&base_attr, 0, sizeof(base_attr));
+ if (!parse_candidate_attr(cands.names[i], &base_attr))
+ continue;
+
+ if (try_open_attr_variants(&base_attr, pmu->is_uncore, pmu_cpu,
+ flags, &pid, &cpu, &last_err)) {
+ pmu->probed.probe_event = strdup(cands.names[i]);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found && pmu->is_core) {
+ memset(&base_attr, 0, sizeof(base_attr));
+ base_attr.type = PERF_TYPE_HARDWARE;
+ base_attr.config = PERF_COUNT_HW_CPU_CYCLES;
+ if (perf_pmus__host_supports_extended_type())
+ base_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
+ if (try_open_attr_variants(&base_attr, false, pmu_cpu,
+ flags, &pid, &cpu, &last_err)) {
+ pmu->probed.probe_event = strdup("cycles");
+ found = true;
+ }
+ }
+
+ if (!found && pmu->type == PERF_TYPE_BREAKPOINT) {
+ memset(&base_attr, 0, sizeof(base_attr));
+ base_attr.type = PERF_TYPE_BREAKPOINT;
+ base_attr.bp_addr = (unsigned long)&dummy_bp_var;
+ base_attr.bp_type = HW_BREAKPOINT_W;
+ base_attr.bp_len = HW_BREAKPOINT_LEN_4;
+ if (try_open_attr_variants(&base_attr, false, pmu_cpu,
+ flags, &pid, &cpu, &last_err)) {
+ pmu->probed.probe_event = strdup("mem:<addr>:w");
+ found = true;
+ }
+ }
+
+ /*
+ * Try an all zero config for PMUs without events. Tracepoint,
+ * breakpoint, kprobe and uprobe PMUs need a config (or address) and
+ * were handled above.
+ */
+ if (!found && cands.nr == 0 && pmu->type != PERF_TYPE_TRACEPOINT &&
+ pmu->type != PERF_TYPE_BREAKPOINT &&
+ !(pmu->name && (!strcmp(pmu->name, "kprobe") || !strcmp(pmu->name, "uprobe")))) {
+ memset(&base_attr, 0, sizeof(base_attr));
+ base_attr.type = pmu->type;
+ base_attr.size = sizeof(base_attr);
+ if (pmu->perf_event_attr_init_default)
+ pmu->perf_event_attr_init_default(pmu, &base_attr);
+ if (try_open_attr_variants(&base_attr, pmu->is_uncore, pmu_cpu,
+ flags, &pid, &cpu, &last_err)) {
+ char buf[256];
+
+ snprintf(buf, sizeof(buf), "%s//", pmu->name);
+ pmu->probed.probe_event = strdup(buf);
+ found = true;
+ }
+ }
+
+ if (!found) {
+ probe_candidates__exit(&cands);
+ pmu->probed.open_supported = false;
+ pmu->probed.open_err = last_err ?: ENOENT;
+ pmu->probed.checked = true;
+ return;
+ }
+
+ pmu->probed.open_supported = true;
+
+ /*
+ * For tracepoint events, closing the last fd for a tracepoint triggers
+ * tracepoint_synchronize_unregister() (an RCU grace period) in the
+ * kernel. Hold one disabled fd open across the probe sequence so
+ * tp_event->perf_refcount stays >= 1 and each probe open/close is
+ * instantaneous.
+ */
+ if (pmu->type == PERF_TYPE_TRACEPOINT) {
+ struct perf_event_attr ka_attr = base_attr;
+
+ ka_attr.disabled = 1;
+ keepalive_fd = sys_perf_event_open(&ka_attr, pid, cpu, -1, flags);
+ }
+
+ probe_candidates__exit(&cands);
+
+ /* 1. Counting vs Sampling support */
+ if (base_attr.sample_period == 0) {
+ struct perf_event_attr samp_attr = base_attr;
+ int fd;
+
+ pmu->probed.can_count = true;
+ samp_attr.sample_period = 10000;
+ samp_attr.sample_type = PERF_SAMPLE_IP;
+ fd = sys_perf_event_open(&samp_attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ pmu->probed.can_sample = true;
+ }
+ } else {
+ pmu->probed.can_count = false;
+ pmu->probed.can_sample = true;
+ }
+
+ if (keepalive_fd >= 0)
+ close(keepalive_fd);
+ pmu->probed.checked = true;
+}
+
+struct probe_thread_args {
+ struct perf_pmu *pmu;
+ int pmu_cpu;
+};
+
+static void *perf_pmu__probe_thread(void *arg)
+{
+ struct probe_thread_args *args = arg;
+
+ if (args->pmu_cpu >= 0 && args->pmu_cpu < CPU_SETSIZE) {
+ cpu_set_t mask;
+
+ CPU_ZERO(&mask);
+ CPU_SET(args->pmu_cpu, &mask);
+ sched_setaffinity(0, sizeof(mask), &mask);
+ }
+ __perf_pmu__probe(args->pmu, args->pmu_cpu);
+ return NULL;
+}
+
+void perf_pmu__probe(struct perf_pmu *pmu)
+{
+ struct probe_thread_args args = {
+ .pmu = pmu,
+ };
+ pthread_t thread;
+
+ if (!pmu || pmu->from_env || pmu->probed.checked)
+ return;
+ if (perf_pmu__kind(pmu) != PERF_PMU_KIND_PE)
+ return;
+
+ args.pmu_cpu = select_probe_cpu(pmu);
+ if (pthread_create(&thread, NULL, perf_pmu__probe_thread, &args) == 0)
+ pthread_join(thread, NULL);
+ else
+ __perf_pmu__probe(pmu, args.pmu_cpu);
+}
+
static bool pmu_config_equal(__u32 type, u64 a, u64 b)
{
/* Ignore the extended PMU type in the upper bits of legacy encodings. */
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index b556ec51fdeb..ddcd28b3cae5 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -212,6 +212,25 @@ struct perf_pmu {
* @mem_events: List of the supported mem events
*/
struct perf_mem_event *mem_events;
+
+ /**
+ * @probed: Capabilities discovered by probing perf_event_open() on live
+ * systems.
+ */
+ struct {
+ /** @probed.checked: Has perf_pmu__probe() been run on this PMU? */
+ bool checked;
+ /** @probed.open_supported: Could a probe event be opened on this PMU? */
+ bool open_supported;
+ /** @probed.can_count: Supports counting (sample_period == 0). */
+ bool can_count;
+ /** @probed.can_sample: Supports interrupt-based sampling (sample_period > 0). */
+ bool can_sample;
+ /** @probed.probe_event: Name of the event used to probe the PMU. */
+ char *probe_event;
+ /** @probed.open_err: errno if open_supported is false. */
+ int open_err;
+ } probed;
};
struct perf_pmu_info {
@@ -334,6 +353,7 @@ bool pmu_uncore_identifier_match(const char *compat, const char *id);
int perf_pmu__convert_scale(const char *scale, char **end, double *sval);
struct perf_pmu_caps *perf_pmu__get_cap(struct perf_pmu *pmu, const char *name);
+
/**
* perf_pmu__add_cap() - Add a name/value capability entry to @pmu->caps.
* @pmu: The PMU to add the capability to.
@@ -343,9 +363,18 @@ struct perf_pmu_caps *perf_pmu__get_cap(struct perf_pmu *pmu, const char *name);
* Return: 0 on success, negative error code on failure.
*/
int perf_pmu__add_cap(struct perf_pmu *pmu, const char *name, const char *value);
-
int perf_pmu__caps_parse(struct perf_pmu *pmu);
+/**
+ * perf_pmu__probe() - Actively probe live kernel/hardware capabilities for @pmu.
+ * @pmu: The PMU to probe via perf_event_open().
+ *
+ * Populates @pmu->probed with supported modes, sample formats, privilege
+ * exclusions, register masks, memory events, group limits, rdpmc, and skid.
+ * No-op if @pmu->probed.checked is already set or @pmu->from_env is true.
+ */
+void perf_pmu__probe(struct perf_pmu *pmu);
+
void perf_pmu__warn_invalid_config(struct perf_pmu *pmu, __u64 config,
const char *name, int config_num,
const char *config_name);
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 04/16] perf pmu: Probe supported sample_type flags
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (2 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 03/16] perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 05/16] perf pmu: Probe precise_ip levels Ian Rogers
` (11 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Extend perf_pmu__probe() with probe_sample_types() to test which enum
perf_event_sample_format flags are supported by the PMU.
First test a common bulk sample_type mask with a single
perf_event_open() call to avoid per-bit syscall overhead when all
standard sample flags are supported, and then probe any remaining
PERF_SAMPLE_* bits individually (configuring required companion fields
such as branch_sample_type, sample_regs_user, sample_regs_intr, and
sample_stack_user). Populate probed.sample_types as well as convenience
booleans for branch stack, callchain, data_src, and weight sampling.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 99 +++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/pmu.h | 13 ++++++
2 files changed, 112 insertions(+)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index be93ff8b9948..d8bcf0e91f6f 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3125,6 +3125,101 @@ static bool try_open_attr_variants(struct perf_event_attr *attr,
return false;
}
+/*
+ * Probe which enum perf_event_sample_format flags are supported by @pmu,
+ * testing a common bulk mask first before probing individual flags.
+ */
+static void probe_sample_types(struct perf_pmu *pmu,
+ const struct perf_event_attr *base_attr,
+ pid_t pid, int cpu, unsigned long flags)
+{
+ int dummy_abi = -1;
+ u64 user_regs = perf_user_reg_mask(EM_HOST, &dummy_abi);
+ u64 intr_regs = perf_intr_reg_mask(EM_HOST, &dummy_abi);
+ u64 user_reg_bit = user_regs ? (user_regs & (~user_regs + 1)) : 1ULL;
+ u64 intr_reg_bit = intr_regs ? (intr_regs & (~intr_regs + 1)) : 1ULL;
+ u64 bulk_mask = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
+ PERF_SAMPLE_ADDR | PERF_SAMPLE_READ | PERF_SAMPLE_CALLCHAIN |
+ PERF_SAMPLE_ID | PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD |
+ PERF_SAMPLE_STREAM_ID | PERF_SAMPLE_RAW | PERF_SAMPLE_REGS_USER |
+ PERF_SAMPLE_STACK_USER | PERF_SAMPLE_WEIGHT | PERF_SAMPLE_DATA_SRC |
+ PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_TRANSACTION |
+ PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_CGROUP |
+ PERF_SAMPLE_DATA_PAGE_SIZE | PERF_SAMPLE_CODE_PAGE_SIZE;
+ struct perf_event_attr b_attr = *base_attr;
+ int b_fd;
+
+ b_attr.disabled = 1;
+ b_attr.sample_period = base_attr->sample_period ?: 10000;
+ b_attr.sample_type = bulk_mask;
+ b_attr.sample_regs_user = user_reg_bit;
+ b_attr.sample_regs_intr = intr_reg_bit;
+ b_attr.sample_stack_user = 64;
+ b_attr.exclude_callchain_kernel = b_attr.exclude_kernel;
+ b_attr.inherit = 0;
+ b_fd = sys_perf_event_open(&b_attr, pid, cpu, -1, flags);
+ if (b_fd >= 0) {
+ close(b_fd);
+ pmu->probed.sample_types |= bulk_mask;
+ }
+
+ for (unsigned int shift = 0; (1ULL << shift) < PERF_SAMPLE_MAX; shift++) {
+ u64 bit = 1ULL << shift;
+ struct perf_event_attr attr = *base_attr;
+ int fd;
+
+ if (pmu->probed.sample_types & bit)
+ continue;
+
+ attr.disabled = 1;
+ attr.sample_period = base_attr->sample_period ?: 10000;
+ attr.sample_type = bit;
+
+ switch (bit) {
+ case PERF_SAMPLE_BRANCH_STACK:
+ attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY |
+ (attr.exclude_kernel ? PERF_SAMPLE_BRANCH_USER : 0);
+ break;
+ case PERF_SAMPLE_CALLCHAIN:
+ attr.exclude_callchain_kernel = attr.exclude_kernel;
+ break;
+ case PERF_SAMPLE_REGS_USER:
+ attr.sample_regs_user = user_reg_bit;
+ break;
+ case PERF_SAMPLE_REGS_INTR:
+ attr.sample_regs_intr = intr_reg_bit;
+ break;
+ case PERF_SAMPLE_STACK_USER:
+ attr.sample_stack_user = 64;
+ break;
+ case PERF_SAMPLE_READ:
+ attr.inherit = 0;
+ break;
+ default:
+ break;
+ }
+
+ fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+ if (fd < 0 && bit == PERF_SAMPLE_BRANCH_STACK) {
+ attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY;
+ fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+ }
+ if (fd < 0 && bit != PERF_SAMPLE_IP) {
+ attr.sample_type = PERF_SAMPLE_IP | bit;
+ fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+ }
+ if (fd >= 0) {
+ close(fd);
+ pmu->probed.sample_types |= bit;
+ }
+ }
+
+ pmu->probed.can_branch_stack = !!(pmu->probed.sample_types & PERF_SAMPLE_BRANCH_STACK);
+ pmu->probed.can_callchain = !!(pmu->probed.sample_types & PERF_SAMPLE_CALLCHAIN);
+ pmu->probed.can_data_src = !!(pmu->probed.sample_types & PERF_SAMPLE_DATA_SRC);
+ pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT);
+}
+
static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
{
struct probe_candidates cands = { .nr = 0 };
@@ -3376,6 +3471,10 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
pmu->probed.can_sample = true;
}
+ /* 2. All enum perf_event_sample_format types */
+ if (pmu->probed.can_sample)
+ probe_sample_types(pmu, &base_attr, pid, cpu, flags);
+
if (keepalive_fd >= 0)
close(keepalive_fd);
pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index ddcd28b3cae5..658485faa05d 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -226,10 +226,23 @@ struct perf_pmu {
bool can_count;
/** @probed.can_sample: Supports interrupt-based sampling (sample_period > 0). */
bool can_sample;
+ /** @probed.can_branch_stack: Supports PERF_SAMPLE_BRANCH_STACK. */
+ bool can_branch_stack;
+ /** @probed.can_callchain: Supports PERF_SAMPLE_CALLCHAIN. */
+ bool can_callchain;
+ /** @probed.can_data_src: Supports PERF_SAMPLE_DATA_SRC. */
+ bool can_data_src;
+ /** @probed.can_weight: Supports PERF_SAMPLE_WEIGHT. */
+ bool can_weight;
/** @probed.probe_event: Name of the event used to probe the PMU. */
char *probe_event;
/** @probed.open_err: errno if open_supported is false. */
int open_err;
+ /**
+ * @probed.sample_types: Bitmask of supported
+ * enum perf_event_sample_format flags.
+ */
+ u64 sample_types;
} probed;
};
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 05/16] perf pmu: Probe precise_ip levels
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (3 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 04/16] perf pmu: Probe supported sample_type flags Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 06/16] perf pmu: Probe privilege exclusion modifiers Ian Rogers
` (10 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Extend perf_pmu__probe() to probe supported precise_ip levels (1..3) and
populate probed.precise_mask and probed.max_precise.
Some PMUs that do not implement hardware precise sampling (such as
software, breakpoint, or AUX trace PMUs) silently ignore attr.precise_ip
even on non-sampling events (sample_period == 0), whereas PMUs that
genuinely validate precise_ip either reject precise_ip > 0 on counting
events or are sampling-only PMUs. Check for this before probing levels
1..3 so non-precise PMUs are not misreported as supporting precise_ip,
while still detecting higher precision levels when core events are
forwarded to hardware sampling units (such as AMD IBS).
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 60 +++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/pmu.h | 4 +++
2 files changed, 64 insertions(+)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index d8bcf0e91f6f..322d90695c4b 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3475,6 +3475,66 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
if (pmu->probed.can_sample)
probe_sample_types(pmu, &base_attr, pid, cpu, flags);
+ /*
+ * 3. Precise sampling support (:p, :pp, :ppp).
+ * PMUs that do not validate attr.precise_ip (such as software,
+ * breakpoint, or intel_pt) silently accept precise_ip > 0 even when
+ * sample_period == 0. A PMU that genuinely implements precise_ip
+ * either rejects precise_ip > 0 on non-sampling events or is a
+ * sampling-only PMU (such as AMD ibs_op).
+ * Note: On AMD, core events like cycles:P are forwarded to IBS and
+ * support higher precision than advertised in sysfs caps/max_precise.
+ */
+ if (pmu->probed.can_sample && !pmu->auxtrace) {
+ bool ignores_precise_ip = false;
+
+ if (pmu->probed.can_count) {
+ struct perf_event_attr c_attr = base_attr;
+ int fd;
+
+ c_attr.sample_period = 0;
+ c_attr.freq = 0;
+ c_attr.sample_type = 0;
+ c_attr.precise_ip = 1;
+ fd = sys_perf_event_open(&c_attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ ignores_precise_ip = true;
+ }
+ }
+
+ if (!ignores_precise_ip) {
+ for (int level = 1; level <= 3; level++) {
+ struct perf_event_attr p_attr = base_attr;
+ int fd;
+
+ p_attr.sample_period = 10000;
+ p_attr.sample_type = PERF_SAMPLE_IP;
+ p_attr.precise_ip = level;
+ fd = sys_perf_event_open(&p_attr, pid, cpu, -1, flags);
+ if (fd < 0 && p_attr.exclude_kernel && p_attr.config2 == 0) {
+ p_attr.config2 = 1;
+ fd = sys_perf_event_open(&p_attr, pid, cpu, -1, flags);
+ }
+ if (fd >= 0) {
+ close(fd);
+ pmu->probed.precise_mask |= (1u << level);
+ pmu->probed.max_precise = level;
+ }
+ }
+ /*
+ * Sampling-only PMUs, like AMD ibs_op, can't be checked
+ * with a counting event. If every level, including 3
+ * that few PMUs support, opened then assume precise_ip
+ * is ignored.
+ */
+ if (!pmu->probed.can_count && pmu->probed.precise_mask == 0xe) {
+ pmu->probed.precise_mask = 0;
+ pmu->probed.max_precise = 0;
+ }
+ }
+ }
+
if (keepalive_fd >= 0)
close(keepalive_fd);
pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 658485faa05d..fea65e314547 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -238,6 +238,10 @@ struct perf_pmu {
char *probe_event;
/** @probed.open_err: errno if open_supported is false. */
int open_err;
+ /** @probed.max_precise: Maximum precise_ip level (0..3) supported. */
+ int max_precise;
+ /** @probed.precise_mask: Bitmask of supported precise_ip levels (1..3). */
+ unsigned int precise_mask;
/**
* @probed.sample_types: Bitmask of supported
* enum perf_event_sample_format flags.
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 06/16] perf pmu: Probe privilege exclusion modifiers
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (4 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 05/16] perf pmu: Probe precise_ip levels Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 07/16] perf pmu: Probe execution and AUX area features Ian Rogers
` (9 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Extend perf_pmu__probe() with test_exclude_flag() to probe whether
exclude_user, exclude_kernel, exclude_hv, exclude_idle, exclude_host,
and exclude_guest are accepted by perf_event_open() on the PMU, storing
the results in struct perf_pmu::probed.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 73 +++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/pmu.h | 12 +++++++
2 files changed, 85 insertions(+)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 322d90695c4b..ab4265154990 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3125,6 +3125,65 @@ static bool try_open_attr_variants(struct perf_event_attr *attr,
return false;
}
+enum probe_exclude_flag {
+ PROBE_EXCLUDE_USER,
+ PROBE_EXCLUDE_KERNEL,
+ PROBE_EXCLUDE_HV,
+ PROBE_EXCLUDE_IDLE,
+ PROBE_EXCLUDE_HOST,
+ PROBE_EXCLUDE_GUEST,
+};
+
+/* Test whether @which privilege exclusion modifier is accepted by perf_event_open(). */
+static bool test_exclude_flag(const struct perf_event_attr *base_attr,
+ pid_t pid, int cpu, unsigned long flags,
+ enum probe_exclude_flag which)
+{
+ struct perf_event_attr attr = *base_attr;
+ int fd;
+
+ switch (which) {
+ case PROBE_EXCLUDE_USER:
+ attr.exclude_user = 1;
+ break;
+ case PROBE_EXCLUDE_KERNEL:
+ attr.exclude_kernel = 1;
+ attr.exclude_user = 0;
+ break;
+ case PROBE_EXCLUDE_HV:
+ attr.exclude_hv = 1;
+ break;
+ case PROBE_EXCLUDE_IDLE:
+ attr.exclude_idle = 1;
+ break;
+ case PROBE_EXCLUDE_HOST:
+ attr.exclude_host = 1;
+ attr.exclude_guest = 0;
+ break;
+ case PROBE_EXCLUDE_GUEST:
+ attr.exclude_guest = 1;
+ attr.exclude_host = 0;
+ break;
+ default:
+ return false;
+ }
+
+ fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ return true;
+ }
+ if (which == PROBE_EXCLUDE_USER && attr.exclude_kernel == 1) {
+ attr.exclude_kernel = 0;
+ fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ return true;
+ }
+ }
+ return false;
+}
+
/*
* Probe which enum perf_event_sample_format flags are supported by @pmu,
* testing a common bulk mask first before probing individual flags.
@@ -3535,6 +3594,20 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
}
}
+ /* 4. Privilege / exclusion modifiers */
+ pmu->probed.exclude_user =
+ test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_USER);
+ pmu->probed.exclude_kernel =
+ test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_KERNEL);
+ pmu->probed.exclude_hv =
+ test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_HV);
+ pmu->probed.exclude_idle =
+ test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_IDLE);
+ pmu->probed.exclude_host =
+ test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_HOST);
+ pmu->probed.exclude_guest =
+ test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_GUEST);
+
if (keepalive_fd >= 0)
close(keepalive_fd);
pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index fea65e314547..a7d794d67bf2 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -234,6 +234,18 @@ struct perf_pmu {
bool can_data_src;
/** @probed.can_weight: Supports PERF_SAMPLE_WEIGHT. */
bool can_weight;
+ /** @probed.exclude_user: Supports perf_event_attr::exclude_user. */
+ bool exclude_user;
+ /** @probed.exclude_kernel: Supports perf_event_attr::exclude_kernel. */
+ bool exclude_kernel;
+ /** @probed.exclude_hv: Supports perf_event_attr::exclude_hv. */
+ bool exclude_hv;
+ /** @probed.exclude_idle: Supports perf_event_attr::exclude_idle. */
+ bool exclude_idle;
+ /** @probed.exclude_host: Supports perf_event_attr::exclude_host. */
+ bool exclude_host;
+ /** @probed.exclude_guest: Supports perf_event_attr::exclude_guest. */
+ bool exclude_guest;
/** @probed.probe_event: Name of the event used to probe the PMU. */
char *probe_event;
/** @probed.open_err: errno if open_supported is false. */
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 07/16] perf pmu: Probe execution and AUX area features
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (5 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 06/16] perf pmu: Probe privilege exclusion modifiers Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 08/16] perf pmu: Probe event group size limits Ian Rogers
` (8 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Extend perf_pmu__probe() to probe event execution and grouping features
via perf_event_open() and ioctl(PERF_EVENT_IOC_ENABLE):
- Group leader sampling (PERF_SAMPLE_READ with PERF_FORMAT_GROUP)
- Grouping software events with the PMU's events (can_sw_grouping)
- Child task inheritance (attr.inherit)
- Dynamic frequency mode (attr.freq)
- Overwrite ring buffer mode (attr.write_backward)
- Pinned and exclusive event attributes (attr.pinned, attr.exclusive)
- Lost-event read format (PERF_FORMAT_LOST)
- Auto-counter reload when the PMU exposes format/acr_mask (can_acr)
- AUX area actions on auxtrace PMUs (aux_start_paused, aux_pause,
aux_resume, and PERF_SAMPLE_AUX snapshot sampling)
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 338 ++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/pmu.h | 32 ++++
2 files changed, 370 insertions(+)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index ab4265154990..a45d42b6e59a 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -2860,6 +2860,7 @@ void perf_pmu__delete(struct perf_pmu *pmu)
free(pmu);
}
+#define MAX_PROBE_GROUP_SIZE 64
#define MAX_PROBE_CANDIDATES 16
#define MAX_TRACEPOINT_PROBE_CANDIDATES 3
@@ -3279,12 +3280,52 @@ static void probe_sample_types(struct perf_pmu *pmu,
pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT);
}
+/*
+ * Pack @value into the perf_event_attr config field named by the sysfs format
+ * @name of @pmu, e.g. "rdpmc" defined as "config1:1". Returns false if the
+ * format doesn't exist or uses an unsupported config field.
+ */
+static bool probe_format_pack(const struct perf_pmu *pmu, struct perf_event_attr *attr,
+ const char *name, __u64 value)
+{
+ struct perf_pmu_format *format = pmu_find_format(&pmu->format, name);
+ __u64 *vp;
+
+ if (!format)
+ return false;
+
+ perf_pmu_format__load(pmu, format);
+ switch (format->value) {
+ case PERF_PMU_FORMAT_VALUE_CONFIG:
+ vp = &attr->config;
+ break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG1:
+ vp = &attr->config1;
+ break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG2:
+ vp = &attr->config2;
+ break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG3:
+ vp = &attr->config3;
+ break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG4:
+ vp = &attr->config4;
+ break;
+ default:
+ return false;
+ }
+ perf_pmu__format_pack(format->bits, value, vp, /*zero=*/true);
+ return true;
+}
+
static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
{
struct probe_candidates cands = { .nr = 0 };
struct perf_event_attr base_attr;
+ struct perf_event_attr group_attrs[MAX_PROBE_GROUP_SIZE];
char uprobe_exe_path[PATH_MAX];
__u64 uprobe_offset = 0;
+ size_t nr_group_attrs = 0;
unsigned long flags = perf_event_open_cloexec_flag();
int keepalive_fd = -1;
pid_t pid = 0;
@@ -3510,6 +3551,48 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
keepalive_fd = sys_perf_event_open(&ka_attr, pid, cpu, -1, flags);
}
+ group_attrs[nr_group_attrs++] = base_attr;
+
+ for (size_t i = 0; pmu->type != PERF_TYPE_TRACEPOINT &&
+ i < cands.nr && nr_group_attrs < MAX_PROBE_GROUP_SIZE; i++) {
+ struct perf_event_attr cand_attr;
+ int fd;
+ bool dup = false;
+
+ memset(&cand_attr, 0, sizeof(cand_attr));
+ if (!parse_candidate_attr(cands.names[i], &cand_attr))
+ continue;
+
+ cand_attr.sample_period = base_attr.sample_period;
+ cand_attr.freq = base_attr.freq;
+ cand_attr.sample_type = base_attr.sample_type;
+ cand_attr.exclude_user = base_attr.exclude_user;
+ cand_attr.exclude_kernel = base_attr.exclude_kernel;
+ cand_attr.exclude_hv = base_attr.exclude_hv;
+ cand_attr.exclude_idle = base_attr.exclude_idle;
+ cand_attr.exclude_host = base_attr.exclude_host;
+ cand_attr.exclude_guest = base_attr.exclude_guest;
+ cand_attr.disabled = 1;
+
+ for (size_t j = 0; j < nr_group_attrs; j++) {
+ if (group_attrs[j].type == cand_attr.type &&
+ group_attrs[j].config == cand_attr.config &&
+ group_attrs[j].config1 == cand_attr.config1 &&
+ group_attrs[j].config2 == cand_attr.config2) {
+ dup = true;
+ break;
+ }
+ }
+ if (dup)
+ continue;
+
+ fd = sys_perf_event_open(&cand_attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ group_attrs[nr_group_attrs++] = cand_attr;
+ }
+ }
+
probe_candidates__exit(&cands);
/* 1. Counting vs Sampling support */
@@ -3608,6 +3691,261 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
pmu->probed.exclude_guest =
test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_GUEST);
+ /* 5. Execution features */
+ if (pmu->probed.can_sample) {
+ struct perf_event_attr l_attr = base_attr;
+ struct perf_event_attr f_attr = nr_group_attrs > 1 ? group_attrs[1] : base_attr;
+ int l_fd;
+
+ l_attr.disabled = 1;
+ l_attr.sample_period = 10000;
+ l_attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_READ;
+ l_attr.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID;
+
+ f_attr.disabled = 0;
+ f_attr.sample_period = 0;
+ f_attr.sample_type = 0;
+ f_attr.read_format = 0;
+
+ l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags);
+ if (l_fd >= 0) {
+ int f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags);
+
+ if (f_fd >= 0) {
+ if (ioctl(l_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) >= 0)
+ pmu->probed.can_leader_sampling = true;
+ ioctl(l_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
+ close(f_fd);
+ }
+ close(l_fd);
+ }
+ }
+
+ {
+ struct perf_event_attr l_attr = base_attr;
+ int l_fd;
+
+ l_attr.disabled = 1;
+ l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags);
+ if (l_fd >= 0) {
+ struct perf_event_attr sw_attr;
+ int sw_fd;
+
+ memset(&sw_attr, 0, sizeof(sw_attr));
+ sw_attr.type = PERF_TYPE_SOFTWARE;
+ sw_attr.size = sizeof(sw_attr);
+ sw_attr.config = PERF_COUNT_SW_DUMMY;
+ sw_attr.disabled = 0;
+ sw_attr.exclude_user = base_attr.exclude_user;
+ sw_attr.exclude_kernel = base_attr.exclude_kernel;
+ sw_attr.exclude_hv = base_attr.exclude_hv;
+ sw_attr.exclude_idle = base_attr.exclude_idle;
+ sw_attr.exclude_host = base_attr.exclude_host;
+ sw_attr.exclude_guest = base_attr.exclude_guest;
+
+ sw_fd = sys_perf_event_open(&sw_attr, pid, cpu, l_fd, flags);
+ if (sw_fd < 0) {
+ sw_attr.config = PERF_COUNT_SW_CPU_CLOCK;
+ sw_fd = sys_perf_event_open(&sw_attr, pid, cpu, l_fd, flags);
+ }
+ if (sw_fd >= 0) {
+ pmu->probed.can_sw_grouping = true;
+ close(sw_fd);
+ }
+ close(l_fd);
+ }
+ }
+
+ {
+ struct perf_event_attr inh_attr = base_attr;
+ int fd;
+
+ inh_attr.disabled = 1;
+ inh_attr.inherit = 1;
+ /*
+ * Inheritance only applies to task events, CPU events (pid
+ * -1) accept but ignore it, so probe with the current task.
+ */
+ fd = sys_perf_event_open(&inh_attr, /*pid=*/0, 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;
+ /* A low frequency is below perf_event_max_sample_rate. */
+ freq_attr.sample_freq = 1;
+ freq_attr.sample_type = PERF_SAMPLE_IP;
+ fd = sys_perf_event_open(&freq_attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ pmu->probed.can_freq = true;
+ }
+ }
+
+ if (pmu->probed.can_sample) {
+ struct perf_event_attr wb_attr = base_attr;
+ int fd;
+
+ wb_attr.disabled = 1;
+ wb_attr.sample_period = 10000;
+ wb_attr.sample_type = PERF_SAMPLE_IP;
+ wb_attr.write_backward = 1;
+ fd = sys_perf_event_open(&wb_attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ pmu->probed.can_write_backward = true;
+ }
+ }
+
+ {
+ struct perf_event_attr pin_attr = base_attr;
+ int fd;
+
+ pin_attr.disabled = 1;
+ pin_attr.pinned = 1;
+ fd = sys_perf_event_open(&pin_attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ pmu->probed.can_pinned = true;
+ }
+ }
+
+ {
+ struct perf_event_attr exc_attr = base_attr;
+ int fd;
+
+ exc_attr.disabled = 1;
+ exc_attr.exclusive = 1;
+ fd = sys_perf_event_open(&exc_attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ pmu->probed.can_exclusive = true;
+ }
+ }
+
+ {
+ struct perf_event_attr lost_attr = base_attr;
+ int fd;
+
+ lost_attr.disabled = 1;
+ lost_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED |
+ PERF_FORMAT_TOTAL_TIME_RUNNING |
+ PERF_FORMAT_LOST;
+ fd = sys_perf_event_open(&lost_attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ pmu->probed.can_format_lost = true;
+ }
+ }
+
+ /* 5c. Auto-counter reload (acr_mask / ratio-to-prev) */
+ if (pmu->probed.can_sample && perf_pmu__has_format(pmu, "acr_mask")) {
+ struct perf_event_attr l_attr = base_attr;
+ struct perf_event_attr f_attr = nr_group_attrs > 1 ? group_attrs[1] : base_attr;
+ int l_fd;
+
+ l_attr.disabled = 1;
+ l_attr.freq = 0;
+ l_attr.sample_period = 200000;
+ l_attr.sample_type = PERF_SAMPLE_IP;
+
+ f_attr.disabled = 0;
+ f_attr.freq = 0;
+ f_attr.sample_period = 100000;
+ f_attr.sample_type = PERF_SAMPLE_IP;
+
+ /*
+ * Equivalent of the group
+ * '{cycles/acr_mask=0x2/,cycles/acr_mask=0x3/}'.
+ */
+ if (probe_format_pack(pmu, &l_attr, "acr_mask", 0x2) &&
+ probe_format_pack(pmu, &f_attr, "acr_mask", 0x3))
+ l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags);
+ else
+ l_fd = -1;
+ if (l_fd >= 0) {
+ int f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags);
+
+ if (f_fd >= 0) {
+ if (ioctl(l_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) >= 0)
+ pmu->probed.can_acr = true;
+ ioctl(l_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
+ close(f_fd);
+ }
+ close(l_fd);
+ }
+ }
+
+ /* 5d. AUX area event actions (start_paused, pause, resume, aux_sample) */
+ if (pmu->auxtrace) {
+ struct perf_event_attr a_attr = base_attr;
+ int l_fd;
+
+ a_attr.size = max_t(__u32, base_attr.size, PERF_ATTR_SIZE_VER8);
+ a_attr.disabled = 1;
+ a_attr.aux_start_paused = 1;
+ l_fd = sys_perf_event_open(&a_attr, pid, cpu, -1, flags);
+ if (l_fd >= 0) {
+ close(l_fd);
+ pmu->probed.can_aux_start_paused = true;
+ }
+
+ a_attr = base_attr;
+ a_attr.disabled = 1;
+ l_fd = sys_perf_event_open(&a_attr, pid, cpu, -1, flags);
+ if (l_fd >= 0) {
+ struct perf_event_attr f_attr;
+ int f_fd;
+
+ memset(&f_attr, 0, sizeof(f_attr));
+ f_attr.type = PERF_TYPE_SOFTWARE;
+ f_attr.size = max_t(__u32, base_attr.size, PERF_ATTR_SIZE_VER8);
+ f_attr.config = PERF_COUNT_SW_CPU_CLOCK;
+ f_attr.sample_period = 10000;
+ f_attr.sample_type = PERF_SAMPLE_IP;
+ f_attr.exclude_user = base_attr.exclude_user;
+ f_attr.exclude_kernel = base_attr.exclude_kernel;
+ f_attr.exclude_hv = base_attr.exclude_hv;
+ f_attr.exclude_idle = base_attr.exclude_idle;
+ f_attr.exclude_host = base_attr.exclude_host;
+ f_attr.exclude_guest = base_attr.exclude_guest;
+
+ f_attr.aux_pause = 1;
+ f_attr.aux_resume = 0;
+ f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags);
+ if (f_fd >= 0) {
+ close(f_fd);
+ pmu->probed.can_aux_pause = true;
+ }
+
+ f_attr.aux_pause = 0;
+ f_attr.aux_resume = 1;
+ f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags);
+ if (f_fd >= 0) {
+ close(f_fd);
+ pmu->probed.can_aux_resume = true;
+ }
+
+ f_attr.aux_resume = 0;
+ f_attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_AUX;
+ f_attr.aux_sample_size = 64;
+ f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags);
+ if (f_fd >= 0) {
+ close(f_fd);
+ pmu->probed.can_aux_sample = true;
+ }
+
+ close(l_fd);
+ }
+ }
+
if (keepalive_fd >= 0)
close(keepalive_fd);
pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index a7d794d67bf2..8efffbac3cbf 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -228,12 +228,44 @@ struct perf_pmu {
bool can_sample;
/** @probed.can_branch_stack: Supports PERF_SAMPLE_BRANCH_STACK. */
bool can_branch_stack;
+ /** @probed.can_leader_sampling: Supports group leader sampling (:S). */
+ bool can_leader_sampling;
+ /**
+ * @probed.can_sw_grouping: Supports grouping software events
+ * with this PMU's events.
+ */
+ bool can_sw_grouping;
+ /** @probed.can_inherit: Supports perf_event_attr::inherit across child tasks. */
+ bool can_inherit;
+ /** @probed.can_freq: Supports dynamic sample frequency (perf_event_attr::freq). */
+ bool can_freq;
+ /** @probed.can_write_backward: Supports overwrite ring buffer (write_backward). */
+ bool can_write_backward;
/** @probed.can_callchain: Supports PERF_SAMPLE_CALLCHAIN. */
bool can_callchain;
/** @probed.can_data_src: Supports PERF_SAMPLE_DATA_SRC. */
bool can_data_src;
/** @probed.can_weight: Supports PERF_SAMPLE_WEIGHT. */
bool can_weight;
+ /** @probed.can_pinned: Supports perf_event_attr::pinned. */
+ bool can_pinned;
+ /** @probed.can_exclusive: Supports perf_event_attr::exclusive. */
+ bool can_exclusive;
+ /** @probed.can_format_lost: Supports PERF_FORMAT_LOST. */
+ bool can_format_lost;
+ /** @probed.can_acr: Supports auto-counter reload (acr_mask / ratio-to-prev). */
+ bool can_acr;
+ /**
+ * @probed.can_aux_start_paused: Supports aux_start_paused
+ * (PERF_PMU_CAP_AUX_PAUSE).
+ */
+ bool can_aux_start_paused;
+ /** @probed.can_aux_pause: Supports aux_pause action on group followers. */
+ bool can_aux_pause;
+ /** @probed.can_aux_resume: Supports aux_resume action on group followers. */
+ bool can_aux_resume;
+ /** @probed.can_aux_sample: Supports AUX area snapshot sampling. */
+ bool can_aux_sample;
/** @probed.exclude_user: Supports perf_event_attr::exclude_user. */
bool exclude_user;
/** @probed.exclude_kernel: Supports perf_event_attr::exclude_kernel. */
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 08/16] perf pmu: Probe event group size limits
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (6 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 07/16] perf pmu: Probe execution and AUX area features Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 09/16] perf pmu: Probe user and interrupt sample register masks Ian Rogers
` (7 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Extend perf_pmu__probe() with check_group_active() and
probe_group_limits() to determine the maximum number of events in a
group that are counted simultaneously, without multiplexing
(max_group_size). After each event is added to the group, the group is
enabled and checked to be counting by comparing time_running with
time_enabled.
The number of events in a group accepted by perf_event_open() isn't
reported as it doesn't reflect what can be counted: disabled events are
accepted so that groups may be scheduled by hand and many PMUs accept
groups of enabled events that can never be scheduled.
On core PMUs, where cycles and instructions may use dedicated fixed
counters, also probe a group of general-purpose (GP) hardware events to
determine max_gp_group_size.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 210 ++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/pmu.h | 14 +++
2 files changed, 224 insertions(+)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index a45d42b6e59a..eb7a4736b30e 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3126,6 +3126,123 @@ static bool try_open_attr_variants(struct perf_event_attr *attr,
return false;
}
+/*
+ * Enable @leader_fd's group briefly and verify that both the group leader and
+ * @last_fd run for 100% of their enabled time without multiplexing.
+ */
+static bool check_group_active(int leader_fd, int last_fd)
+{
+ u64 before[3] = { 0, 0, 0 };
+ u64 after[3] = { 0, 0, 0 };
+ u64 f_before[3] = { 0, 0, 0 };
+ u64 f_after[3] = { 0, 0, 0 };
+ u64 delta_ena, delta_run;
+ bool check_follower = false;
+
+ if (read(leader_fd, before, sizeof(before)) != sizeof(before))
+ return false;
+
+ if (last_fd >= 0 && read(last_fd, f_before, sizeof(f_before)) == sizeof(f_before))
+ check_follower = true;
+
+ if (ioctl(leader_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) < 0)
+ return false;
+
+ for (int spin = 0; spin < 20000; spin++)
+ barrier();
+
+ ioctl(leader_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
+
+ if (read(leader_fd, after, sizeof(after)) != sizeof(after))
+ return false;
+
+ if (check_follower && read(last_fd, f_after, sizeof(f_after)) != sizeof(f_after))
+ return false;
+
+ delta_ena = after[1] - before[1];
+ delta_run = after[2] - before[2];
+ if (delta_ena == 0 || delta_run != delta_ena)
+ return false;
+
+ if (check_follower) {
+ u64 f_delta_ena = f_after[1] - f_before[1];
+ u64 f_delta_run = f_after[2] - f_before[2];
+
+ if (f_delta_ena == 0 || f_delta_run != f_delta_ena)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Probe the maximum number of events in a group that are counted
+ * simultaneously, without multiplexing. Opening a group doesn't imply it can
+ * be scheduled, many PMUs accept groups of enabled events that can never be
+ * counted, so after each follower is added the group is enabled and checked
+ * to be running.
+ */
+static void probe_group_limits(const struct perf_event_attr *attrs, size_t nr_attrs,
+ pid_t pid, int cpu, unsigned long flags,
+ int *max_size, bool *capped)
+{
+ struct perf_event_attr leader_attr;
+ int leader_fd;
+ int fds[MAX_PROBE_GROUP_SIZE];
+ int nr_followers = 0;
+ enum rlimit_action set_rlimit = NO_CHANGE;
+
+ *max_size = 0;
+ *capped = false;
+
+ if (!attrs || nr_attrs == 0)
+ return;
+
+ leader_attr = attrs[0];
+ leader_attr.disabled = 1;
+ leader_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
+
+ /* The times are needed to determine whether the group is counting. */
+ leader_fd = sys_perf_event_open(&leader_attr, pid, cpu, -1, flags);
+ if (leader_fd < 0)
+ return;
+
+ if (!check_group_active(leader_fd, -1))
+ goto out;
+ *max_size = 1;
+
+ for (int count = 2; count <= MAX_PROBE_GROUP_SIZE; count++) {
+ struct perf_event_attr follower_attr = attrs[(count - 1) % nr_attrs];
+ int fd;
+
+ follower_attr.disabled = 0;
+ follower_attr.read_format = leader_attr.read_format;
+
+ fd = sys_perf_event_open(&follower_attr, pid, cpu, leader_fd, flags);
+ if (fd < 0 && errno == EMFILE && rlimit__increase_nofile(&set_rlimit))
+ fd = sys_perf_event_open(&follower_attr, pid, cpu, leader_fd, flags);
+ if (fd < 0) {
+ /* The leader's times show whether the whole group is counting. */
+ follower_attr.read_format = 0;
+ fd = sys_perf_event_open(&follower_attr, pid, cpu, leader_fd, flags);
+ }
+ if (fd < 0)
+ break;
+
+ fds[nr_followers++] = fd;
+ if (!check_group_active(leader_fd, fd))
+ break;
+ *max_size = count;
+ }
+
+ if (*max_size >= MAX_PROBE_GROUP_SIZE)
+ *capped = true;
+out:
+ for (int i = 0; i < nr_followers; i++)
+ close(fds[i]);
+ close(leader_fd);
+}
+
enum probe_exclude_flag {
PROBE_EXCLUDE_USER,
PROBE_EXCLUDE_KERNEL,
@@ -3946,6 +4063,99 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
}
}
+ /* 6. Max number of events in a group counted simultaneously. */
+ if (pmu->name && (!strcmp(pmu->name, "kprobe") || !strcmp(pmu->name, "uprobe"))) {
+ if (pmu->probed.can_leader_sampling || pmu->probed.can_sw_grouping) {
+ pmu->probed.max_group_size = MAX_PROBE_GROUP_SIZE;
+ pmu->probed.max_group_capped = true;
+ } else {
+ pmu->probed.max_group_size = 1;
+ }
+ } else {
+ probe_group_limits(group_attrs, nr_group_attrs, pid, cpu, flags,
+ &pmu->probed.max_group_size,
+ &pmu->probed.max_group_capped);
+ }
+
+ /*
+ * On core PMUs, cycles/instructions may use fixed counters in addition
+ * to general-purpose (GP) programmable counters. Probe a GP-only event
+ * group rotating through distinct GP events to determine the GP limit.
+ */
+ if (pmu->is_core) {
+ static const char * const gp_events[] = {
+ "branches",
+ "branch-misses",
+ "cache-misses",
+ "cache-references",
+ "bus-cycles",
+ "stalled-cycles-frontend",
+ "stalled-cycles-backend",
+ };
+ static const int gp_configs[] = {
+ PERF_COUNT_HW_BRANCH_INSTRUCTIONS,
+ PERF_COUNT_HW_BRANCH_MISSES,
+ PERF_COUNT_HW_CACHE_MISSES,
+ PERF_COUNT_HW_CACHE_REFERENCES,
+ PERF_COUNT_HW_BUS_CYCLES,
+ PERF_COUNT_HW_STALLED_CYCLES_FRONTEND,
+ PERF_COUNT_HW_STALLED_CYCLES_BACKEND,
+ };
+ struct perf_event_attr gp_attrs[MAX_PROBE_GROUP_SIZE];
+ size_t nr_gp_attrs = 0;
+ pid_t gp_pid = pid;
+ int gp_cpu = cpu;
+ int gp_err = 0;
+
+ for (size_t i = 0;
+ i < ARRAY_SIZE(gp_events) && nr_gp_attrs < MAX_PROBE_GROUP_SIZE; i++) {
+ char gp_buf[256];
+ struct perf_event_attr gp_attr;
+ bool parsed;
+
+ snprintf(gp_buf, sizeof(gp_buf), "%s/%s/", pmu->name, gp_events[i]);
+ memset(&gp_attr, 0, sizeof(gp_attr));
+ parsed = parse_candidate_attr(gp_buf, &gp_attr);
+ if (!parsed) {
+ gp_attr.type = PERF_TYPE_HARDWARE;
+ gp_attr.size = sizeof(gp_attr);
+ gp_attr.config = gp_configs[i];
+ if (perf_pmus__host_supports_extended_type())
+ gp_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
+ }
+ gp_attr.sample_period = base_attr.sample_period;
+ gp_attr.freq = base_attr.freq;
+ gp_attr.sample_type = base_attr.sample_type;
+ gp_attr.exclude_user = base_attr.exclude_user;
+ gp_attr.exclude_kernel = base_attr.exclude_kernel;
+ gp_attr.exclude_hv = base_attr.exclude_hv;
+ gp_attr.exclude_idle = base_attr.exclude_idle;
+ gp_attr.exclude_host = base_attr.exclude_host;
+ gp_attr.exclude_guest = base_attr.exclude_guest;
+ gp_attr.disabled = 1;
+
+ if (nr_gp_attrs == 0) {
+ if (try_open_attr_variants(&gp_attr, false, pmu_cpu,
+ flags, &gp_pid, &gp_cpu, &gp_err)) {
+ gp_attrs[nr_gp_attrs++] = gp_attr;
+ }
+ } else {
+ int fd = sys_perf_event_open(&gp_attr, gp_pid, gp_cpu, -1, flags);
+
+ if (fd >= 0) {
+ close(fd);
+ gp_attrs[nr_gp_attrs++] = gp_attr;
+ }
+ }
+ }
+
+ if (nr_gp_attrs > 0) {
+ probe_group_limits(gp_attrs, nr_gp_attrs, gp_pid, gp_cpu, flags,
+ &pmu->probed.max_gp_group_size,
+ &pmu->probed.max_gp_group_capped);
+ }
+ }
+
if (keepalive_fd >= 0)
close(keepalive_fd);
pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 8efffbac3cbf..cacb8bc5198b 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -278,6 +278,10 @@ struct perf_pmu {
bool exclude_host;
/** @probed.exclude_guest: Supports perf_event_attr::exclude_guest. */
bool exclude_guest;
+ /** @probed.max_group_capped: Was max_group_size capped at probe limit? */
+ bool max_group_capped;
+ /** @probed.max_gp_group_capped: Was max_gp_group_size capped at probe limit? */
+ bool max_gp_group_capped;
/** @probed.probe_event: Name of the event used to probe the PMU. */
char *probe_event;
/** @probed.open_err: errno if open_supported is false. */
@@ -286,6 +290,16 @@ struct perf_pmu {
int max_precise;
/** @probed.precise_mask: Bitmask of supported precise_ip levels (1..3). */
unsigned int precise_mask;
+ /**
+ * @probed.max_group_size: Max events in a group counted
+ * simultaneously, without multiplexing.
+ */
+ int max_group_size;
+ /**
+ * @probed.max_gp_group_size: Max general-purpose counter events
+ * in a group counted simultaneously, without multiplexing.
+ */
+ int max_gp_group_size;
/**
* @probed.sample_types: Bitmask of supported
* enum perf_event_sample_format flags.
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 09/16] perf pmu: Probe user and interrupt sample register masks
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (7 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 08/16] perf pmu: Probe event group size limits Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 10/16] perf pmu: Probe interrupt registers that require precise sampling Ian Rogers
` (6 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Extend perf_pmu__probe() with probe_pmu_regs_one() to probe supported
register sampling masks for PERF_SAMPLE_REGS_USER and
PERF_SAMPLE_REGS_INTR when supported by the PMU.
Test the architecture's base general-purpose register mask as a batch
before falling back to per-register probing if needed, and on hardware
PMUs also probe extended GPRs (such as x86 APX R16-R31 and CET SSP) and
SIMD vector/predicate register classes via PERF_SAMPLE_REGS_ABI_SIMD.
Without the SIMD ABI, probe registers like the x86 XMM registers. Add
perf_reg_probe_candidates() so the architecture provides the candidate
extended registers for each ABI and sampling direction.
Signed-off-by: Ian Rogers <irogers@google.com>
---
.../perf/util/perf-regs-arch/perf_regs_x86.c | 10 +
tools/perf/util/perf_regs.c | 16 ++
tools/perf/util/perf_regs.h | 2 +
tools/perf/util/pmu.c | 244 ++++++++++++++++++
tools/perf/util/pmu.h | 28 ++
5 files changed, 300 insertions(+)
diff --git a/tools/perf/util/perf-regs-arch/perf_regs_x86.c b/tools/perf/util/perf-regs-arch/perf_regs_x86.c
index 8cb9dfa6c7d3..6dd2439d5a4a 100644
--- a/tools/perf/util/perf-regs-arch/perf_regs_x86.c
+++ b/tools/perf/util/perf-regs-arch/perf_regs_x86.c
@@ -318,6 +318,16 @@ uint64_t __perf_reg_mask_x86(bool intr, int *abi)
return mask;
}
+uint64_t __perf_reg_probe_candidates_x86(bool intr __maybe_unused, bool simd)
+{
+ /* With the SIMD ABI, the APX eGPRs and CET SSP. */
+ if (simd)
+ return GENMASK_ULL(PERF_REG_X86_R31, PERF_REG_X86_R16) | BIT_ULL(PERF_REG_X86_SSP);
+
+ /* Otherwise the XMM registers, supported via XSAVES or PEBS. */
+ return PERF_REG_EXTENDED_MASK;
+}
+
static const char *__arch_reg_gpr_name(int id)
{
switch (id) {
diff --git a/tools/perf/util/perf_regs.c b/tools/perf/util/perf_regs.c
index c8242b7e1db5..76be1f2f823f 100644
--- a/tools/perf/util/perf_regs.c
+++ b/tools/perf/util/perf_regs.c
@@ -275,6 +275,22 @@ uint64_t perf_user_simd_reg_class_mask(uint16_t e_machine, bool pred)
}
}
+/*
+ * Registers, beyond the basic general purpose registers, that a PMU may
+ * support sampling and that should be probed for with perf_event_open(). The
+ * PERF_SAMPLE_REGS_ABI_SIMD ABI (@simd) may change the meaning of bits.
+ */
+uint64_t perf_reg_probe_candidates(uint16_t e_machine, bool intr, bool simd)
+{
+ switch (e_machine) {
+ case EM_386:
+ case EM_X86_64:
+ return __perf_reg_probe_candidates_x86(intr, simd);
+ default:
+ return 0;
+ }
+}
+
uint64_t perf_intr_simd_reg_class_bitmap_qwords(uint16_t e_machine, int reg_c,
uint16_t *qwords, bool pred)
{
diff --git a/tools/perf/util/perf_regs.h b/tools/perf/util/perf_regs.h
index c9f461b6f11f..095dd603bca9 100644
--- a/tools/perf/util/perf_regs.h
+++ b/tools/perf/util/perf_regs.h
@@ -27,6 +27,7 @@ uint64_t perf_intr_simd_reg_class_bitmap_qwords(uint16_t e_machine, int reg_c,
uint64_t perf_user_simd_reg_class_bitmap_qwords(uint16_t e_machine, int reg_c,
uint16_t *qwords, bool pred);
const char *perf_simd_reg_class_name(uint16_t e_machine, int id, bool pred);
+uint64_t perf_reg_probe_candidates(uint16_t e_machine, bool intr, bool simd);
int __perf_sdt_arg_parse_op_arm64(char *old_op, char **new_op);
uint64_t __perf_reg_mask_arm64(bool intr);
@@ -81,6 +82,7 @@ uint64_t __perf_simd_reg_class_mask_x86(bool intr, bool pred);
uint64_t __perf_simd_reg_class_bitmap_qwords_x86(int reg_c, uint16_t *qwords,
bool intr, bool pred);
const char *__perf_simd_reg_class_name_x86(int id, bool pred);
+uint64_t __perf_reg_probe_candidates_x86(bool intr, bool simd);
static inline uint64_t DWARF_MINIMAL_REGS(uint16_t e_machine)
{
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index eb7a4736b30e..cf1bf1a871e9 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3397,6 +3397,232 @@ static void probe_sample_types(struct perf_pmu *pmu,
pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT);
}
+/*
+ * Probe supported general-purpose, extended GPR, and SIMD vector/predicate
+ * register sampling masks for either PERF_SAMPLE_REGS_USER (!@intr) or
+ * PERF_SAMPLE_REGS_INTR (@intr).
+ */
+static void probe_pmu_regs_one(const struct perf_pmu *pmu,
+ const struct perf_event_attr *base_attr,
+ pid_t pid, int cpu, unsigned long flags,
+ int max_precise, bool intr,
+ u64 *out_mask, int *out_abi,
+ u64 *out_simd_mask, u64 *out_pred_mask)
+{
+ int dummy_abi = -1;
+ u64 gpr_mask = intr ? perf_intr_reg_mask(EM_HOST, &dummy_abi) :
+ perf_user_reg_mask(EM_HOST, &dummy_abi);
+ u64 sample_type = intr ? PERF_SAMPLE_REGS_INTR : PERF_SAMPLE_REGS_USER;
+ u64 mask = 0;
+ int abi = 0;
+ struct perf_event_attr attr = *base_attr;
+ int fd;
+
+ attr.disabled = 1;
+ attr.sample_period = base_attr->sample_period ?: 10000;
+ attr.sample_type = sample_type;
+
+ if (gpr_mask) {
+ if (intr)
+ attr.sample_regs_intr = gpr_mask;
+ else
+ attr.sample_regs_user = gpr_mask;
+
+ fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ mask |= gpr_mask;
+ } else {
+ for (int b = 0; b < 64; b++) {
+ if (!(gpr_mask & BIT_ULL(b)))
+ continue;
+ if (intr)
+ attr.sample_regs_intr = BIT_ULL(b);
+ else
+ attr.sample_regs_user = BIT_ULL(b);
+ fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ mask |= BIT_ULL(b);
+ }
+ }
+ }
+ }
+
+ if (perf_pmu__is_software(pmu)) {
+ *out_mask = mask;
+ *out_abi = abi;
+ return;
+ }
+
+ /*
+ * Probe extended GPRs outside the base GPR mask with the
+ * PERF_SAMPLE_REGS_ABI_SIMD ABI (e.g. APX R16-R31 and CET SSP).
+ * Note: perf_pmu__probe() actively probes the running host kernel via
+ * perf_event_open(), so EM_HOST is used intentionally here.
+ */
+ {
+ u64 ext_gpr_mask = perf_reg_probe_candidates(EM_HOST, intr, /*simd=*/true) &
+ ~gpr_mask;
+
+ if (ext_gpr_mask) {
+ struct perf_event_attr e_attr = *base_attr;
+ bool ext_batch_ok = false;
+
+ e_attr.size = sizeof(e_attr);
+ e_attr.disabled = 1;
+ e_attr.sample_period = base_attr->sample_period ?: 10000;
+ e_attr.sample_type = sample_type;
+ e_attr.sample_simd_regs_enabled = 1;
+ if (intr)
+ e_attr.sample_regs_intr = ext_gpr_mask;
+ else
+ e_attr.sample_regs_user = ext_gpr_mask;
+
+ fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
+ if (fd < 0 && intr && max_precise > 0) {
+ e_attr.precise_ip = 1;
+ fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
+ }
+ if (fd >= 0) {
+ close(fd);
+ mask |= ext_gpr_mask;
+ abi |= PERF_SAMPLE_REGS_ABI_SIMD;
+ ext_batch_ok = true;
+ }
+
+ if (!ext_batch_ok) {
+ for (int b = 0; b < 64; b++) {
+ if (!(ext_gpr_mask & BIT_ULL(b)))
+ continue;
+ e_attr.precise_ip = base_attr->precise_ip;
+ if (intr)
+ e_attr.sample_regs_intr = BIT_ULL(b);
+ else
+ e_attr.sample_regs_user = BIT_ULL(b);
+ fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
+ if (fd < 0 && intr && max_precise > 0) {
+ e_attr.precise_ip = 1;
+ fd = sys_perf_event_open(&e_attr, pid, cpu, -1,
+ flags);
+ }
+ if (fd >= 0) {
+ close(fd);
+ mask |= BIT_ULL(b);
+ abi |= PERF_SAMPLE_REGS_ABI_SIMD;
+ }
+ }
+ }
+ }
+ }
+
+ for (int is_pred = 0; is_pred <= 1; is_pred++) {
+ bool pred = is_pred != 0;
+ u64 class_mask = intr ?
+ perf_intr_simd_reg_class_mask(EM_HOST, pred) :
+ perf_user_simd_reg_class_mask(EM_HOST, pred);
+ u64 supported_classes = 0;
+
+ for (int reg_c = 0; reg_c < 64; reg_c++) {
+ struct perf_event_attr s_attr = *base_attr;
+ u16 qwords = 0;
+ u64 bitmap;
+
+ if (!(class_mask & BIT_ULL(reg_c)))
+ continue;
+ bitmap = intr ?
+ perf_intr_simd_reg_class_bitmap_qwords(EM_HOST, reg_c,
+ &qwords, pred) :
+ perf_user_simd_reg_class_bitmap_qwords(EM_HOST, reg_c,
+ &qwords, pred);
+ if (!bitmap || !qwords)
+ continue;
+
+ s_attr.size = sizeof(s_attr);
+ s_attr.disabled = 1;
+ s_attr.sample_period = base_attr->sample_period ?: 10000;
+ s_attr.sample_type = sample_type;
+ s_attr.sample_simd_regs_enabled = 1;
+ if (pred) {
+ s_attr.sample_simd_pred_reg_qwords = qwords;
+ if (intr)
+ s_attr.sample_simd_pred_reg_intr = bitmap;
+ else
+ s_attr.sample_simd_pred_reg_user = bitmap;
+ } else {
+ s_attr.sample_simd_vec_reg_qwords = qwords;
+ if (intr)
+ s_attr.sample_simd_vec_reg_intr = bitmap;
+ else
+ s_attr.sample_simd_vec_reg_user = bitmap;
+ }
+
+ fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags);
+ if (fd < 0 && intr && max_precise > 0) {
+ s_attr.precise_ip = 1;
+ fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags);
+ }
+ if (fd >= 0) {
+ close(fd);
+ supported_classes |= BIT_ULL(reg_c);
+ abi |= PERF_SAMPLE_REGS_ABI_SIMD;
+ }
+ }
+
+ if (pred)
+ *out_pred_mask = supported_classes;
+ else
+ *out_simd_mask = supported_classes;
+ }
+
+ if (!(abi & PERF_SAMPLE_REGS_ABI_SIMD)) {
+ /* Registers like x86 XMM without the SIMD ABI. */
+ u64 ext_mask = perf_reg_probe_candidates(EM_HOST, intr, /*simd=*/false) &
+ ~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++) {
+ if (!(ext_mask & BIT_ULL(b)))
+ continue;
+ if (intr)
+ x_attr.sample_regs_intr = BIT_ULL(b);
+ else
+ x_attr.sample_regs_user = BIT_ULL(b);
+ fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags);
+ if (fd >= 0) {
+ close(fd);
+ mask |= BIT_ULL(b);
+ }
+ }
+ }
+ }
+
+ *out_mask = mask;
+ *out_abi = abi;
+}
+
/*
* Pack @value into the perf_event_attr config field named by the sysfs format
* @name of @pmu, e.g. "rdpmc" defined as "config1:1". Returns false if the
@@ -3794,6 +4020,24 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
}
}
+ /* 3b. User and interrupt register sampling masks */
+ if (pmu->probed.sample_types & PERF_SAMPLE_REGS_USER) {
+ probe_pmu_regs_one(pmu, &base_attr, pid, cpu, flags,
+ pmu->probed.max_precise, /*intr=*/false,
+ &pmu->probed.user_regs_mask,
+ &pmu->probed.user_regs_abi,
+ &pmu->probed.user_simd_mask,
+ &pmu->probed.user_pred_mask);
+ }
+ if (pmu->probed.sample_types & PERF_SAMPLE_REGS_INTR) {
+ probe_pmu_regs_one(pmu, &base_attr, pid, cpu, flags,
+ pmu->probed.max_precise, /*intr=*/true,
+ &pmu->probed.intr_regs_mask,
+ &pmu->probed.intr_regs_abi,
+ &pmu->probed.intr_simd_mask,
+ &pmu->probed.intr_pred_mask);
+ }
+
/* 4. Privilege / exclusion modifiers */
pmu->probed.exclude_user =
test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_USER);
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index cacb8bc5198b..66ee3841872a 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -300,11 +300,39 @@ struct perf_pmu {
* in a group counted simultaneously, without multiplexing.
*/
int max_gp_group_size;
+ /** @probed.user_regs_abi: ABI flags for user_regs_mask. */
+ int user_regs_abi;
+ /** @probed.intr_regs_abi: ABI flags for intr_regs_mask. */
+ int intr_regs_abi;
/**
* @probed.sample_types: Bitmask of supported
* enum perf_event_sample_format flags.
*/
u64 sample_types;
+ /** @probed.user_regs_mask: Bitmask of supported sample_regs_user registers. */
+ u64 user_regs_mask;
+ /** @probed.intr_regs_mask: Bitmask of supported sample_regs_intr registers. */
+ u64 intr_regs_mask;
+ /**
+ * @probed.user_simd_mask: Bitmask of supported user SIMD vector
+ * register classes.
+ */
+ u64 user_simd_mask;
+ /**
+ * @probed.user_pred_mask: Bitmask of supported user SIMD predicate
+ * register classes.
+ */
+ u64 user_pred_mask;
+ /**
+ * @probed.intr_simd_mask: Bitmask of supported intr SIMD vector
+ * register classes.
+ */
+ u64 intr_simd_mask;
+ /**
+ * @probed.intr_pred_mask: Bitmask of supported intr SIMD predicate
+ * register classes.
+ */
+ u64 intr_pred_mask;
} probed;
};
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 10/16] perf pmu: Probe interrupt registers that require precise sampling
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (8 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 09/16] perf pmu: Probe user and interrupt sample register masks Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 11/16] perf pmu: Probe memory profiling events and sample formats Ian Rogers
` (5 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Some interrupt registers can only be sampled with precise sampling, for
example on x86 the XMM registers are captured in PEBS records and
x86_pmu_hw_config() rejects them in sample_regs_intr without precise_ip.
The interrupt register probe tried precise sampling as a fallback, or
for the legacy extended registers unconditionally, so the probed mask
didn't show which registers require it.
Add probe_regs_open() that opens without precise sampling first and
records when only the precise open succeeds, and record the interrupt
registers and SIMD register classes that require precise sampling in
intr_regs_precise_mask, intr_simd_precise_mask and
intr_pred_precise_mask.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 117 +++++++++++++++++++++++++++---------------
tools/perf/util/pmu.h | 16 ++++++
2 files changed, 92 insertions(+), 41 deletions(-)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index cf1bf1a871e9..80159358318e 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3397,18 +3397,50 @@ static void probe_sample_types(struct perf_pmu *pmu,
pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT);
}
+/*
+ * Open @attr, if that fails and @try_precise then retry with precise sampling,
+ * setting @needs_precise when only the precise open succeeds. Returns whether
+ * either open succeeded.
+ */
+static bool probe_regs_open(struct perf_event_attr *attr, pid_t pid, int cpu,
+ unsigned long flags, bool try_precise, bool *needs_precise)
+{
+ u8 precise_ip = attr->precise_ip;
+ int fd = sys_perf_event_open(attr, pid, cpu, -1, flags);
+
+ *needs_precise = false;
+ if (fd < 0 && try_precise && !precise_ip) {
+ attr->precise_ip = 1;
+ fd = sys_perf_event_open(attr, pid, cpu, -1, flags);
+ attr->precise_ip = precise_ip;
+ *needs_precise = fd >= 0;
+ }
+ if (fd < 0)
+ return false;
+ close(fd);
+ return true;
+}
+
/*
* Probe supported general-purpose, extended GPR, and SIMD vector/predicate
* register sampling masks for either PERF_SAMPLE_REGS_USER (!@intr) or
- * PERF_SAMPLE_REGS_INTR (@intr).
+ * PERF_SAMPLE_REGS_INTR (@intr). For interrupt registers, the registers and
+ * register classes that can only be sampled with precise sampling are
+ * recorded in @out_precise_mask, @out_simd_precise_mask and
+ * @out_pred_precise_mask, which may be NULL for user registers.
*/
static void probe_pmu_regs_one(const struct perf_pmu *pmu,
const struct perf_event_attr *base_attr,
pid_t pid, int cpu, unsigned long flags,
int max_precise, bool intr,
u64 *out_mask, int *out_abi,
- u64 *out_simd_mask, u64 *out_pred_mask)
+ u64 *out_simd_mask, u64 *out_pred_mask,
+ u64 *out_precise_mask, u64 *out_simd_precise_mask,
+ u64 *out_pred_precise_mask)
{
+ bool try_precise = intr && max_precise > 0;
+ u64 precise_mask = 0, simd_precise_mask = 0, pred_precise_mask = 0;
+ bool needs_precise;
int dummy_abi = -1;
u64 gpr_mask = intr ? perf_intr_reg_mask(EM_HOST, &dummy_abi) :
perf_user_reg_mask(EM_HOST, &dummy_abi);
@@ -3479,14 +3511,11 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
else
e_attr.sample_regs_user = ext_gpr_mask;
- fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
- if (fd < 0 && intr && max_precise > 0) {
- e_attr.precise_ip = 1;
- fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
- }
- if (fd >= 0) {
- close(fd);
+ if (probe_regs_open(&e_attr, pid, cpu, flags, try_precise,
+ &needs_precise)) {
mask |= ext_gpr_mask;
+ if (needs_precise)
+ precise_mask |= ext_gpr_mask;
abi |= PERF_SAMPLE_REGS_ABI_SIMD;
ext_batch_ok = true;
}
@@ -3495,22 +3524,16 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
for (int b = 0; b < 64; b++) {
if (!(ext_gpr_mask & BIT_ULL(b)))
continue;
- e_attr.precise_ip = base_attr->precise_ip;
if (intr)
e_attr.sample_regs_intr = BIT_ULL(b);
else
e_attr.sample_regs_user = BIT_ULL(b);
- fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags);
- if (fd < 0 && intr && max_precise > 0) {
- e_attr.precise_ip = 1;
- fd = sys_perf_event_open(&e_attr, pid, cpu, -1,
- flags);
- }
- if (fd >= 0) {
- close(fd);
- mask |= BIT_ULL(b);
- abi |= PERF_SAMPLE_REGS_ABI_SIMD;
- }
+ if (!probe_regs_open(&e_attr, pid, cpu, flags,
+ try_precise, &needs_precise))
+ continue;
+ mask |= BIT_ULL(b);
+ precise_mask |= needs_precise ? BIT_ULL(b) : 0;
+ abi |= PERF_SAMPLE_REGS_ABI_SIMD;
}
}
}
@@ -3521,7 +3544,7 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
u64 class_mask = intr ?
perf_intr_simd_reg_class_mask(EM_HOST, pred) :
perf_user_simd_reg_class_mask(EM_HOST, pred);
- u64 supported_classes = 0;
+ u64 supported_classes = 0, precise_classes = 0;
for (int reg_c = 0; reg_c < 64; reg_c++) {
struct perf_event_attr s_attr = *base_attr;
@@ -3557,22 +3580,22 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
s_attr.sample_simd_vec_reg_user = bitmap;
}
- fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags);
- if (fd < 0 && intr && max_precise > 0) {
- s_attr.precise_ip = 1;
- fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags);
- }
- if (fd >= 0) {
- close(fd);
+ if (probe_regs_open(&s_attr, pid, cpu, flags, try_precise,
+ &needs_precise)) {
supported_classes |= BIT_ULL(reg_c);
+ if (needs_precise)
+ precise_classes |= BIT_ULL(reg_c);
abi |= PERF_SAMPLE_REGS_ABI_SIMD;
}
}
- if (pred)
+ if (pred) {
*out_pred_mask = supported_classes;
- else
+ pred_precise_mask = precise_classes;
+ } else {
*out_simd_mask = supported_classes;
+ simd_precise_mask = precise_classes;
+ }
}
if (!(abi & PERF_SAMPLE_REGS_ABI_SIMD)) {
@@ -3585,8 +3608,6 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
x_attr.disabled = 1;
x_attr.sample_period = base_attr->sample_period ?: 10000;
x_attr.sample_type = sample_type;
- if (intr && max_precise > 0)
- x_attr.precise_ip = 1;
if (ext_mask) {
if (intr)
@@ -3594,10 +3615,11 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
else
x_attr.sample_regs_user = ext_mask;
- fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags);
- if (fd >= 0) {
- close(fd);
+ if (probe_regs_open(&x_attr, pid, cpu, flags, try_precise,
+ &needs_precise)) {
mask |= ext_mask;
+ if (needs_precise)
+ precise_mask |= ext_mask;
batch_ok = true;
}
}
@@ -3610,10 +3632,11 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
x_attr.sample_regs_intr = BIT_ULL(b);
else
x_attr.sample_regs_user = BIT_ULL(b);
- fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags);
- if (fd >= 0) {
- close(fd);
+ if (probe_regs_open(&x_attr, pid, cpu, flags, try_precise,
+ &needs_precise)) {
mask |= BIT_ULL(b);
+ if (needs_precise)
+ precise_mask |= BIT_ULL(b);
}
}
}
@@ -3621,6 +3644,12 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
*out_mask = mask;
*out_abi = abi;
+ if (out_precise_mask)
+ *out_precise_mask = precise_mask;
+ if (out_simd_precise_mask)
+ *out_simd_precise_mask = simd_precise_mask;
+ if (out_pred_precise_mask)
+ *out_pred_precise_mask = pred_precise_mask;
}
/*
@@ -4027,7 +4056,10 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
&pmu->probed.user_regs_mask,
&pmu->probed.user_regs_abi,
&pmu->probed.user_simd_mask,
- &pmu->probed.user_pred_mask);
+ &pmu->probed.user_pred_mask,
+ /*out_precise_mask=*/NULL,
+ /*out_simd_precise_mask=*/NULL,
+ /*out_pred_precise_mask=*/NULL);
}
if (pmu->probed.sample_types & PERF_SAMPLE_REGS_INTR) {
probe_pmu_regs_one(pmu, &base_attr, pid, cpu, flags,
@@ -4035,7 +4067,10 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
&pmu->probed.intr_regs_mask,
&pmu->probed.intr_regs_abi,
&pmu->probed.intr_simd_mask,
- &pmu->probed.intr_pred_mask);
+ &pmu->probed.intr_pred_mask,
+ &pmu->probed.intr_regs_precise_mask,
+ &pmu->probed.intr_simd_precise_mask,
+ &pmu->probed.intr_pred_precise_mask);
}
/* 4. Privilege / exclusion modifiers */
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 66ee3841872a..21c4b1a4b608 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -333,6 +333,22 @@ struct perf_pmu {
* register classes.
*/
u64 intr_pred_mask;
+ /**
+ * @probed.intr_regs_precise_mask: Subset of intr_regs_mask that
+ * can only be sampled with precise sampling, such as x86 XMM
+ * registers that are captured by PEBS.
+ */
+ u64 intr_regs_precise_mask;
+ /**
+ * @probed.intr_simd_precise_mask: Subset of intr_simd_mask that
+ * can only be sampled with precise sampling.
+ */
+ u64 intr_simd_precise_mask;
+ /**
+ * @probed.intr_pred_precise_mask: Subset of intr_pred_mask that
+ * can only be sampled with precise sampling.
+ */
+ u64 intr_pred_precise_mask;
} probed;
};
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 11/16] perf pmu: Probe memory profiling events and sample formats
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (9 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 10/16] perf pmu: Probe interrupt registers that require precise sampling Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 12/16] perf pmu: Probe user-space mmap capabilities Ian Rogers
` (4 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Extend perf_pmu__probe() with try_open_mem_evlist() and
probe_mem_events() to test which perf_mem_events types (such as
ldlat-loads and ldlat-stores) can be opened on the PMU and which
additional memory sample formats (PERF_SAMPLE_WEIGHT,
PERF_SAMPLE_WEIGHT_STRUCT, PERF_SAMPLE_PHYS_ADDR,
PERF_SAMPLE_DATA_PAGE_SIZE, PERF_SAMPLE_TRANSACTION) are supported with
them.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 139 ++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/pmu.h | 7 +++
2 files changed, 146 insertions(+)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 80159358318e..45c557c85c5e 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3652,6 +3652,142 @@ static void probe_pmu_regs_one(const struct perf_pmu *pmu,
*out_pred_precise_mask = pred_precise_mask;
}
+static bool try_open_mem_evlist(struct evlist *evlist,
+ const struct perf_event_attr *base_attr,
+ pid_t pid, int cpu, unsigned long flags,
+ int precise_ip, u64 sample_type)
+{
+ int fds[16];
+ size_t nr_fds = 0;
+ int leader_fd = -1;
+ struct evsel *evsel;
+ bool ok = true;
+
+ evlist__for_each_entry(evlist, evsel) {
+ struct perf_event_attr attr = evsel->core.attr;
+ int fd;
+
+ if (nr_fds >= ARRAY_SIZE(fds)) {
+ ok = false;
+ break;
+ }
+
+ attr.disabled = (leader_fd < 0) ? 1 : 0;
+ attr.sample_period = attr.sample_period ?: 10000;
+ attr.sample_type = sample_type;
+ attr.exclude_user = base_attr->exclude_user;
+ attr.exclude_kernel = base_attr->exclude_kernel;
+ attr.exclude_hv = base_attr->exclude_hv;
+ attr.exclude_idle = base_attr->exclude_idle;
+ attr.exclude_host = base_attr->exclude_host;
+ attr.exclude_guest = base_attr->exclude_guest;
+ if (evsel->precise_max || precise_ip > 0)
+ attr.precise_ip = precise_ip;
+
+ fd = sys_perf_event_open(&attr, pid, cpu, leader_fd, flags);
+ if (fd < 0) {
+ ok = false;
+ break;
+ }
+ if (leader_fd < 0)
+ leader_fd = fd;
+ fds[nr_fds++] = fd;
+ }
+
+ for (size_t i = nr_fds; i > 0; i--)
+ close(fds[i - 1]);
+
+ return ok && nr_fds > 0;
+}
+
+/*
+ * Probe which perf_mem_events types (e.g. ldlat-loads, ldlat-stores) and
+ * additional memory sample formats can be opened on @pmu.
+ */
+static void probe_mem_events(struct perf_pmu *pmu,
+ const struct perf_event_attr *base_attr,
+ pid_t pid, int cpu, unsigned long flags)
+{
+ static const u64 extra_mem_stypes[] = {
+ PERF_SAMPLE_WEIGHT,
+ PERF_SAMPLE_WEIGHT_STRUCT,
+ PERF_SAMPLE_PHYS_ADDR,
+ PERF_SAMPLE_DATA_PAGE_SIZE,
+ PERF_SAMPLE_TRANSACTION,
+ };
+
+ if (!pmu->mem_events)
+ return;
+
+ perf_pmu__mem_events_init_pmu(pmu);
+
+ for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
+ struct perf_mem_event *e = perf_pmu__mem_events_ptr(pmu, j);
+ const char *s;
+ char buf[256];
+ struct evlist *evlist;
+ struct parse_events_error err;
+ struct evsel *first;
+ int max_p, min_p, working_p = -1;
+ u64 base_stype = PERF_SAMPLE_IP | PERF_SAMPLE_ADDR | PERF_SAMPLE_DATA_SRC;
+ int ret;
+
+ if (!e || !e->tag || !e->supported)
+ continue;
+
+ s = perf_pmu__mem_events_name(pmu, j, buf, sizeof(buf));
+ if (!s)
+ continue;
+
+ evlist = evlist__new();
+ if (!evlist)
+ continue;
+
+ parse_events_error__init(&err);
+ ret = __parse_events(evlist, s, /*pmu_filter=*/NULL,
+ /*cputype_filter=*/false, &err, /*fake_pmu=*/false,
+ /*warn_if_reordered=*/false, /*fake_tp=*/false);
+ parse_events_error__exit(&err);
+ if (ret || evlist__empty(evlist)) {
+ evlist__put(evlist);
+ continue;
+ }
+
+ first = evlist__first(evlist);
+ if (first->precise_max) {
+ max_p = pmu->probed.max_precise > 0 ? pmu->probed.max_precise : 3;
+ min_p = 1;
+ } else {
+ max_p = first->core.attr.precise_ip;
+ min_p = first->core.attr.precise_ip;
+ }
+
+ for (int p = max_p; p >= min_p; p--) {
+ if (try_open_mem_evlist(evlist, base_attr, pid, cpu,
+ flags, p, base_stype)) {
+ working_p = p;
+ break;
+ }
+ }
+
+ if (working_p >= 0) {
+ pmu->probed.mem_events_mask |= (1u << j);
+ pmu->probed.mem_sample_types |= PERF_SAMPLE_ADDR | PERF_SAMPLE_DATA_SRC;
+
+ for (size_t k = 0; k < ARRAY_SIZE(extra_mem_stypes); k++) {
+ u64 st = base_stype | extra_mem_stypes[k];
+
+ if (try_open_mem_evlist(evlist, base_attr, pid, cpu,
+ flags, working_p, st)) {
+ pmu->probed.mem_sample_types |= extra_mem_stypes[k];
+ }
+ }
+ }
+
+ evlist__put(evlist);
+ }
+}
+
/*
* Pack @value into the perf_event_attr config field named by the sysfs format
* @name of @pmu, e.g. "rdpmc" defined as "config1:1". Returns false if the
@@ -4073,6 +4209,9 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
&pmu->probed.intr_pred_precise_mask);
}
+ /* 3c. Memory event types and memory sample formats */
+ probe_mem_events(pmu, &base_attr, pid, cpu, flags);
+
/* 4. Privilege / exclusion modifiers */
pmu->probed.exclude_user =
test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_USER);
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 21c4b1a4b608..ee56570cac00 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -304,11 +304,18 @@ struct perf_pmu {
int user_regs_abi;
/** @probed.intr_regs_abi: ABI flags for intr_regs_mask. */
int intr_regs_abi;
+ /** @probed.mem_events_mask: Bitmask of memory event types verified by probing. */
+ unsigned int mem_events_mask;
/**
* @probed.sample_types: Bitmask of supported
* enum perf_event_sample_format flags.
*/
u64 sample_types;
+ /**
+ * @probed.mem_sample_types: Bitmask of memory sample formats
+ * verified on mem_events.
+ */
+ u64 mem_sample_types;
/** @probed.user_regs_mask: Bitmask of supported sample_regs_user registers. */
u64 user_regs_mask;
/** @probed.intr_regs_mask: Bitmask of supported sample_regs_intr registers. */
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 12/16] perf pmu: Probe user-space mmap capabilities
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (10 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 11/16] perf pmu: Probe memory profiling events and sample formats Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 13/16] perf pmu: Probe branch instruction skid Ian Rogers
` (3 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Extend perf_pmu__probe() with probe_mmap_caps() to inspect the
perf_event_mmap_page of an opened event on the PMU.
Record whether user-space counter reading (rdpmc on x86, mrs on arm64
with format/rdpmc=1) is available in sysfs/sysctl and enabled in
cap_user_rdpmc, verify whether executing a self-read via
perf_mmap__read_self() produces non-zero advancing counts
(rdpmc_works), and record pmc_width and cap_user_time /
cap_user_time_zero / cap_user_time_short support.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 111 ++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/pmu.h | 25 ++++++++++
2 files changed, 136 insertions(+)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 45c557c85c5e..04bd5b1bc33f 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3826,6 +3826,114 @@ static bool probe_format_pack(const struct perf_pmu *pmu, struct perf_event_attr
return true;
}
+/*
+ * Probe user-space perf_event_mmap_page capabilities, including user-space
+ * counter reading (rdpmc/mrs/csrr), counter bit-width, and user time fields.
+ */
+static void probe_mmap_caps(struct perf_pmu *pmu,
+ const struct perf_event_attr *base_attr,
+ pid_t pid, int cpu, unsigned long flags)
+{
+ struct perf_event_attr attr = *base_attr;
+ struct perf_event_mmap_page *pc;
+ long page_sz = sysconf(_SC_PAGESIZE);
+ bool has_rdpmc_cap = false;
+ int events_fd;
+ int fd = -1;
+
+ pmu->probed.rdpmc_sysfs = -1;
+
+ if (page_sz <= 0)
+ page_sz = 4096;
+
+ events_fd = perf_pmu__event_source_devices_fd();
+ if (events_fd >= 0) {
+ int pmu_dirfd = openat(events_fd, pmu->name, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
+
+ if (pmu_dirfd >= 0) {
+ if (faccessat(pmu_dirfd, "rdpmc", F_OK, 0) == 0) {
+ int rfd;
+
+ has_rdpmc_cap = true;
+ rfd = openat(pmu_dirfd, "rdpmc", O_RDONLY | O_CLOEXEC);
+ if (rfd >= 0) {
+ char rbuf[16];
+ ssize_t n = read(rfd, rbuf, sizeof(rbuf) - 1);
+
+ if (n > 0) {
+ rbuf[n] = '\0';
+ pmu->probed.rdpmc_sysfs = atoi(rbuf);
+ }
+ close(rfd);
+ }
+ }
+ close(pmu_dirfd);
+ }
+ close(events_fd);
+ }
+
+ attr.disabled = 0;
+
+ /*
+ * Architectures such as arm64 and RISC-V control user-space counter
+ * reading for core PMUs with a sysctl rather than a sysfs rdpmc file.
+ */
+ if (pmu->is_core && pmu->probed.rdpmc_sysfs < 0) {
+ int uval;
+
+ if (sysctl__read_int("kernel/perf_user_access", &uval) == 0) {
+ has_rdpmc_cap = true;
+ pmu->probed.rdpmc_sysfs = uval;
+ }
+ }
+
+ /*
+ * On architectures such as arm64, user-space counter reading must be
+ * explicitly requested via format/rdpmc (e.g. config1:1).
+ */
+ if (perf_pmu__has_format(pmu, "rdpmc")) {
+ struct perf_event_attr r_attr = attr;
+
+ has_rdpmc_cap = true;
+ if (probe_format_pack(pmu, &r_attr, "rdpmc", 1))
+ fd = sys_perf_event_open(&r_attr, 0, -1, -1, flags);
+ }
+
+ if (fd < 0 && !pmu->is_uncore)
+ fd = sys_perf_event_open(&attr, 0, -1, -1, flags);
+ if (fd < 0)
+ fd = sys_perf_event_open(&attr, pid, cpu, -1, flags);
+
+ if (fd >= 0) {
+ pc = mmap(NULL, page_sz, PROT_READ, MAP_SHARED, fd, 0);
+ if (pc != MAP_FAILED) {
+ if (has_rdpmc_cap || pc->cap_user_rdpmc) {
+ pmu->probed.rdpmc_available = true;
+ pmu->probed.pmc_width = pc->pmc_width;
+ }
+ if (pc->cap_user_rdpmc && pc->index != 0) {
+ struct perf_mmap map = { .base = pc };
+ struct perf_counts_values start = { .val = 0 };
+ struct perf_counts_values end = { .val = 0 };
+
+ pmu->probed.can_user_rdpmc = true;
+ if (perf_mmap__read_self(&map, &start) == 0) {
+ for (int spin = 0; spin < 10000; spin++)
+ barrier();
+ if (perf_mmap__read_self(&map, &end) == 0 &&
+ end.val > start.val)
+ pmu->probed.rdpmc_works = true;
+ }
+ }
+ pmu->probed.can_user_time = !!pc->cap_user_time;
+ pmu->probed.can_user_time_zero = !!pc->cap_user_time_zero;
+ pmu->probed.can_user_time_short = !!pc->cap_user_time_short;
+ munmap(pc, page_sz);
+ }
+ close(fd);
+ }
+}
+
static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
{
struct probe_candidates cands = { .nr = 0 };
@@ -4380,6 +4488,9 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
}
}
+ /* 5b. User-space mmap capabilities (rdpmc, user_time, pmc_width) */
+ probe_mmap_caps(pmu, &base_attr, pid, cpu, flags);
+
/* 5c. Auto-counter reload (acr_mask / ratio-to-prev) */
if (pmu->probed.can_sample && perf_pmu__has_format(pmu, "acr_mask")) {
struct perf_event_attr l_attr = base_attr;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index ee56570cac00..bb21e910da90 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -266,6 +266,24 @@ struct perf_pmu {
bool can_aux_resume;
/** @probed.can_aux_sample: Supports AUX area snapshot sampling. */
bool can_aux_sample;
+ /**
+ * @probed.rdpmc_available: PMU supports user-space counter
+ * reading (rdpmc/mrs/csrr).
+ */
+ bool rdpmc_available;
+ /**
+ * @probed.can_user_rdpmc: User-space counter reading enabled
+ * (cap_user_rdpmc & index).
+ */
+ bool can_user_rdpmc;
+ /** @probed.rdpmc_works: User-space counter read instruction executed and worked. */
+ bool rdpmc_works;
+ /** @probed.can_user_time: Supports user-space time conversion (cap_user_time). */
+ bool can_user_time;
+ /** @probed.can_user_time_zero: Supports cap_user_time_zero in mmap page. */
+ bool can_user_time_zero;
+ /** @probed.can_user_time_short: Supports cap_user_time_short in mmap page. */
+ bool can_user_time_short;
/** @probed.exclude_user: Supports perf_event_attr::exclude_user. */
bool exclude_user;
/** @probed.exclude_kernel: Supports perf_event_attr::exclude_kernel. */
@@ -304,8 +322,15 @@ struct perf_pmu {
int user_regs_abi;
/** @probed.intr_regs_abi: ABI flags for intr_regs_mask. */
int intr_regs_abi;
+ /**
+ * @probed.rdpmc_sysfs: Value from sysfs rdpmc or perf_user_access
+ * (-1 if unknown).
+ */
+ int rdpmc_sysfs;
/** @probed.mem_events_mask: Bitmask of memory event types verified by probing. */
unsigned int mem_events_mask;
+ /** @probed.pmc_width: Bit-width of hardware counter for user-space rdpmc. */
+ u16 pmc_width;
/**
* @probed.sample_types: Bitmask of supported
* enum perf_event_sample_format flags.
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 13/16] perf pmu: Probe branch instruction skid
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (11 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 12/16] perf pmu: Probe user-space mmap capabilities Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:55 ` [PATCH v2 14/16] perf sysinfo: Add sysinfo subcommand Ian Rogers
` (2 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 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
Extend perf_pmu__probe() with run_skid_branch_loop() and
probe_pmu_skid() to empirically measure branch instruction skid across
supported precise_ip levels (0..3) on core PMUs.
For each supported precise_ip level, open a branch sampling event on the
current thread, execute a NOP-runway loop ending in a backward
conditional branch on x86_64, arm64, and riscv, and compute the minimum,
maximum, average, and exact-IP (zero-skid) sample counts from the
recorded ring buffer samples.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 259 ++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/pmu.h | 15 +++
2 files changed, 274 insertions(+)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 04bd5b1bc33f..8157ada08502 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3934,6 +3934,263 @@ static void probe_mmap_caps(struct perf_pmu *pmu,
}
}
+/*
+ * Mark noinline so the compiler does not inline the multi-kilobyte .rept nop
+ * runway into callers; GCC estimates inline asm size by counting newlines in
+ * the template string and would otherwise emit short-range conditional
+ * branches (such as ARM64 tbz/tbnz with a +-32KB range) across the inlined
+ * block.
+ */
+static noinline bool run_skid_branch_loop(unsigned long iters,
+ uintptr_t *out_s_ip,
+ uintptr_t *out_b_ip,
+ uintptr_t *out_e_ip,
+ int *out_insn_sz, int *out_nop_cnt)
+{
+#if defined(__x86_64__)
+ uintptr_t s_ip, b_ip, e_ip;
+
+ asm volatile(
+ "lea 1f(%%rip), %[s_ip]\n\t"
+ "lea 2f(%%rip), %[b_ip]\n\t"
+ "lea 3f(%%rip), %[e_ip]\n\t"
+ ".balign 64\n"
+ "1:\n\t"
+ ".rept 8192\n\t"
+ "nop\n\t"
+ ".endr\n\t"
+ "dec %[cnt]\n\t"
+ "2:\n\t"
+ "jnz 1b\n"
+ "3:\n\t"
+ : [cnt] "+r" (iters),
+ [s_ip] "=&r" (s_ip),
+ [b_ip] "=&r" (b_ip),
+ [e_ip] "=&r" (e_ip)
+ :
+ : "cc", "memory"
+ );
+ *out_s_ip = s_ip;
+ *out_b_ip = b_ip;
+ *out_e_ip = e_ip;
+ *out_insn_sz = 1;
+ *out_nop_cnt = 8192;
+ return true;
+#elif defined(__aarch64__)
+ uintptr_t s_ip, b_ip, e_ip;
+
+ asm volatile(
+ "adr %[s_ip], 1f\n\t"
+ "adr %[b_ip], 2f\n\t"
+ "adr %[e_ip], 3f\n\t"
+ ".balign 64\n"
+ "1:\n\t"
+ ".rept 1024\n\t"
+ "nop\n\t"
+ ".endr\n\t"
+ "subs %[cnt], %[cnt], #1\n\t"
+ "2:\n\t"
+ "b.ne 1b\n"
+ "3:\n\t"
+ : [cnt] "+r" (iters),
+ [s_ip] "=&r" (s_ip),
+ [b_ip] "=&r" (b_ip),
+ [e_ip] "=&r" (e_ip)
+ :
+ : "cc", "memory"
+ );
+ *out_s_ip = s_ip;
+ *out_b_ip = b_ip;
+ *out_e_ip = e_ip;
+ *out_insn_sz = 4;
+ *out_nop_cnt = 1024;
+ return true;
+#elif defined(__riscv)
+ uintptr_t s_ip, b_ip, e_ip;
+
+ asm volatile(
+ ".option push\n\t"
+ ".option norvc\n\t"
+ "la %[s_ip], 1f\n\t"
+ "la %[b_ip], 2f\n\t"
+ "la %[e_ip], 3f\n\t"
+ ".balign 64\n"
+ "1:\n\t"
+ ".rept 512\n\t"
+ "nop\n\t"
+ ".endr\n\t"
+ "addi %[cnt], %[cnt], -1\n\t"
+ "2:\n\t"
+ "bnez %[cnt], 1b\n"
+ "3:\n\t"
+ ".option pop\n\t"
+ : [cnt] "+r" (iters),
+ [s_ip] "=&r" (s_ip),
+ [b_ip] "=&r" (b_ip),
+ [e_ip] "=&r" (e_ip)
+ :
+ : "memory"
+ );
+ *out_s_ip = s_ip;
+ *out_b_ip = b_ip;
+ *out_e_ip = e_ip;
+ *out_insn_sz = 4;
+ *out_nop_cnt = 512;
+ return true;
+#else
+ (void)iters;
+ *out_s_ip = 0;
+ *out_b_ip = 0;
+ *out_e_ip = 0;
+ *out_insn_sz = 0;
+ *out_nop_cnt = 0;
+ return false;
+#endif
+}
+
+/*
+ * Measure branch instruction skid across supported precise_ip levels (0..3)
+ * by sampling a tight NOP-runway branch loop and inspecting sample IPs.
+ */
+static void probe_pmu_skid(struct perf_pmu *pmu,
+ const struct perf_event_attr *base_attr,
+ unsigned long flags)
+{
+ struct perf_event_attr br_attr;
+ char br_buf[256];
+ long page_sz = sysconf(_SC_PAGESIZE);
+ size_t data_sz, mmap_sz;
+
+ if (!pmu->is_core || !pmu->probed.can_sample)
+ return;
+
+ if (page_sz <= 0)
+ page_sz = 4096;
+ data_sz = 4 * (size_t)page_sz;
+ mmap_sz = (size_t)page_sz + data_sz;
+
+ memset(&br_attr, 0, sizeof(br_attr));
+ snprintf(br_buf, sizeof(br_buf), "%s/branches/", pmu->name);
+ if (!parse_candidate_attr(br_buf, &br_attr)) {
+ br_attr.type = PERF_TYPE_HARDWARE;
+ br_attr.size = base_attr->size;
+ br_attr.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS;
+ if (perf_pmus__host_supports_extended_type())
+ br_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
+ }
+ br_attr.exclude_user = base_attr->exclude_user;
+ br_attr.exclude_kernel = 1;
+ br_attr.exclude_hv = 1;
+ br_attr.exclude_idle = base_attr->exclude_idle;
+ br_attr.exclude_host = base_attr->exclude_host;
+ br_attr.exclude_guest = base_attr->exclude_guest;
+
+ for (int lvl = 0; lvl <= 3; lvl++) {
+ struct perf_event_attr attr = br_attr;
+ struct perf_event_mmap_page *pc;
+ uintptr_t s_ip = 0, b_ip = 0, e_ip = 0;
+ int insn_sz = 0, nop_cnt = 0;
+ void *ring;
+ u8 *data;
+ u64 head, tail = 0;
+ int samples = 0, exact = 0;
+ int min_skid = 999999, max_skid = 0;
+ long sum_skid = 0;
+ int fd;
+
+ if (lvl > 0 && !(pmu->probed.precise_mask & (1u << lvl)))
+ continue;
+
+ attr.disabled = 1;
+ attr.sample_period = 251;
+ attr.sample_type = PERF_SAMPLE_IP;
+ attr.precise_ip = lvl;
+
+ fd = sys_perf_event_open(&attr, 0, -1, -1, flags);
+ if (fd < 0 && lvl > 0 && attr.exclude_kernel && attr.config2 == 0) {
+ attr.config2 = 1;
+ fd = sys_perf_event_open(&attr, 0, -1, -1, flags);
+ }
+ if (fd < 0)
+ continue;
+
+ ring = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (ring == MAP_FAILED) {
+ close(fd);
+ continue;
+ }
+
+ ioctl(fd, PERF_EVENT_IOC_RESET, 0);
+ ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
+ if (!run_skid_branch_loop(8000, &s_ip, &b_ip, &e_ip,
+ &insn_sz, &nop_cnt)) {
+ ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
+ munmap(ring, mmap_sz);
+ close(fd);
+ break;
+ }
+ ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
+
+ pc = ring;
+ head = READ_ONCE(pc->data_head);
+ /* Ensure data_head is read before reading ring buffer contents. */
+ rmb();
+ if (head > data_sz)
+ head = data_sz;
+ data = (u8 *)ring + page_sz;
+
+ while (tail + sizeof(struct perf_event_header) <= head) {
+ struct perf_event_header *hdr =
+ (struct perf_event_header *)(data + tail);
+
+ if (hdr->size == 0 || tail + hdr->size > head)
+ break;
+ if (hdr->type == PERF_RECORD_SAMPLE &&
+ hdr->size >= sizeof(*hdr) + sizeof(u64)) {
+ u64 ip = *(u64 *)((u8 *)hdr + sizeof(*hdr));
+ uintptr_t uip = (uintptr_t)ip;
+
+ if (uip >= s_ip && uip <= e_ip && insn_sz > 0) {
+ int skid;
+
+ if (uip == b_ip) {
+ skid = 0;
+ } else if (uip >= s_ip && uip < b_ip) {
+ long off = (long)(uip - s_ip) / insn_sz;
+
+ skid = (off < nop_cnt) ?
+ (int)(off + 1) : (nop_cnt + 1);
+ } else {
+ skid = 1;
+ }
+
+ samples++;
+ if (hdr->misc & PERF_RECORD_MISC_EXACT_IP)
+ exact++;
+ if (skid < min_skid)
+ min_skid = skid;
+ if (skid > max_skid)
+ max_skid = skid;
+ sum_skid += skid;
+ }
+ }
+ tail += hdr->size;
+ }
+
+ if (samples > 0) {
+ pmu->probed.skid[lvl].measured = true;
+ pmu->probed.skid[lvl].samples = samples;
+ pmu->probed.skid[lvl].exact_samples = exact;
+ pmu->probed.skid[lvl].min_skid = min_skid;
+ pmu->probed.skid[lvl].max_skid = max_skid;
+ pmu->probed.skid[lvl].avg_skid = (double)sum_skid / samples;
+ }
+
+ munmap(ring, mmap_sz);
+ close(fd);
+ }
+}
+
static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
{
struct probe_candidates cands = { .nr = 0 };
@@ -4291,6 +4548,8 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
pmu->probed.max_precise = 0;
}
}
+
+ probe_pmu_skid(pmu, &base_attr, flags);
}
/* 3b. User and interrupt register sampling masks */
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index bb21e910da90..4bdb8daf7a5a 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -381,6 +381,21 @@ struct perf_pmu {
* can only be sampled with precise sampling.
*/
u64 intr_pred_precise_mask;
+ /** @probed.skid: Measured branch instruction skid at precise_ip levels (0..3). */
+ struct {
+ /** @probed.skid.measured: Was skid measured at this precise_ip level? */
+ bool measured;
+ /** @probed.skid.samples: Number of branch samples collected. */
+ int samples;
+ /** @probed.skid.exact_samples: Samples with zero skid from target IP. */
+ int exact_samples;
+ /** @probed.skid.min_skid: Minimum instruction skid observed. */
+ int min_skid;
+ /** @probed.skid.max_skid: Maximum instruction skid observed. */
+ int max_skid;
+ /** @probed.skid.avg_skid: Average instruction skid across samples. */
+ double avg_skid;
+ } skid[4];
} probed;
};
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 14/16] perf sysinfo: Add sysinfo subcommand
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (12 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 13/16] perf pmu: Probe branch instruction skid Ian Rogers
@ 2026-11-13 19:55 ` Ian Rogers
2026-11-13 19:56 ` [PATCH v2 15/16] perf docs: Add perf-sysinfo man page Ian Rogers
2026-11-13 19:56 ` [PATCH v2 16/16] perf test: Add perf sysinfo shell test Ian Rogers
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:55 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
Cc: linux-perf-users, linux-kernel
Add the 'perf sysinfo' subcommand to display and probe PMUs and system
performance monitoring capabilities on the running system or from a
perf.data file.
By default, 'perf sysinfo' reports a system-wide summary of core PMU
features (precise sampling, branch stack sampling, user-space counter
reading, leader sampling, memory profiling events, and hardware AUX
trace PMUs), memory and PCIe/IO interconnect bus bandwidth monitoring,
total events and metrics, pipeline metric drill-down levels, and an
overall system rating.
When '-p'/'--pmus' (or a PMU glob filter, '-v'/'--verbose',
'-a'/'--all', or '-j'/'--json') is specified, it also reports per-PMU
static attributes and live probed capabilities in text or JSON format.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/Build | 1 +
tools/perf/builtin-help.c | 3 +-
tools/perf/builtin-sysinfo.c | 2024 ++++++++++++++++++++++++++++++++++
tools/perf/builtin.h | 1 +
tools/perf/perf.c | 1 +
5 files changed, 2029 insertions(+), 1 deletion(-)
create mode 100644 tools/perf/builtin-sysinfo.c
diff --git a/tools/perf/Build b/tools/perf/Build
index dcba5ba55206..8a3f6071fa40 100644
--- a/tools/perf/Build
+++ b/tools/perf/Build
@@ -13,6 +13,7 @@ perf-y += builtin-list.o
perf-y += builtin-record.o
perf-y += builtin-report.o
perf-y += builtin-stat.o
+perf-y += builtin-sysinfo.o
perf-y += builtin-top.o
perf-y += builtin-script.o
perf-y += builtin-kvm.o
diff --git a/tools/perf/builtin-help.c b/tools/perf/builtin-help.c
index 5bbdb4d9b9c0..fbab4b77761b 100644
--- a/tools/perf/builtin-help.c
+++ b/tools/perf/builtin-help.c
@@ -329,6 +329,7 @@ void list_common_cmds_help(void)
{"report", "Read perf.data (created by perf record) and display the profile"},
{"script", "Read perf.data (created by perf record) and display trace output"},
{"stat", "Run a command and gather performance counter statistics"},
+ {"sysinfo", "Display and probe PMU and system performance monitoring information"},
{"test", "Runs sanity tests."},
{"top", "System profiling tool."},
{"version", "display the version of perf binary"},
@@ -485,7 +486,7 @@ int cmd_help(int argc, const char **argv)
};
const char * const builtin_help_subcommands[] = {
"buildid-cache", "buildid-list", "diff", "evlist", "help", "list",
- "record", "report", "bench", "stat", "timechart", "top", "annotate",
+ "record", "report", "bench", "stat", "sysinfo", "timechart", "top", "annotate",
"script", "sched", "kallsyms", "kmem", "lock", "kvm", "test", "inject", "mem", "data",
#ifdef HAVE_LIBELF_SUPPORT
"probe",
diff --git a/tools/perf/builtin-sysinfo.c b/tools/perf/builtin-sysinfo.c
new file mode 100644
index 000000000000..8774c8ffc0b9
--- /dev/null
+++ b/tools/perf/builtin-sysinfo.c
@@ -0,0 +1,2024 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * builtin-sysinfo.c
+ *
+ * Builtin sysinfo command: Display and probe PMU and system performance
+ * monitoring information in a system or a perf.data file.
+ */
+#include <errno.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#include <dwarf-regs.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/perf_event.h>
+#include <linux/zalloc.h>
+#include <subcmd/parse-options.h>
+
+#include "builtin.h"
+
+#include "util/color.h"
+#include "util/cpumap.h"
+#include "util/data.h"
+#include "util/debug.h"
+#include "util/env.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/header.h"
+#include "util/mem-events.h"
+#include "util/metricgroup.h"
+#include "util/perf_regs.h"
+#include "util/pmu.h"
+#include "util/pmus.h"
+#include "util/session.h"
+#include "util/strbuf.h"
+#include "util/string2.h"
+#include "util/strlist.h"
+#include "util/tool.h"
+
+static const char *pmu_type_desc(u32 type)
+{
+ switch (type) {
+ case PERF_TYPE_HARDWARE:
+ return "PERF_TYPE_HARDWARE";
+ case PERF_TYPE_SOFTWARE:
+ return "PERF_TYPE_SOFTWARE";
+ case PERF_TYPE_TRACEPOINT:
+ return "PERF_TYPE_TRACEPOINT";
+ case PERF_TYPE_HW_CACHE:
+ return "PERF_TYPE_HW_CACHE";
+ case PERF_TYPE_RAW:
+ return "PERF_TYPE_RAW";
+ case PERF_TYPE_BREAKPOINT:
+ return "PERF_TYPE_BREAKPOINT";
+ default:
+ if (type <= PERF_PMU_TYPE_PE_END)
+ return "dynamic";
+ if (type <= PERF_PMU_TYPE_DRM_END)
+ return "drm";
+ if (type <= PERF_PMU_TYPE_HWMON_END)
+ return "hwmon";
+ if (type == PERF_PMU_TYPE_TOOL)
+ return "tool";
+ return "fake";
+ }
+}
+
+static const char *format_config_name(int config)
+{
+ static const char * const config_names[PERF_PMU_FORMAT_VALUE_CONFIG_END] = {
+ [PERF_PMU_FORMAT_VALUE_CONFIG] = "config",
+ [PERF_PMU_FORMAT_VALUE_CONFIG1] = "config1",
+ [PERF_PMU_FORMAT_VALUE_CONFIG2] = "config2",
+ [PERF_PMU_FORMAT_VALUE_CONFIG3] = "config3",
+ [PERF_PMU_FORMAT_VALUE_CONFIG4] = "config4",
+ };
+
+ if (config >= 0 && config < PERF_PMU_FORMAT_VALUE_CONFIG_END)
+ return config_names[config];
+ return "config?";
+}
+
+static bool pmu_one_matches_filter(const struct perf_pmu *pmu, const char *pat)
+{
+ if (perf_pmu__wildcard_match(pmu, pat))
+ return true;
+ if (pmu->name && (strglobmatch(pmu->name, pat) || !strcmp(pmu->name, pat)))
+ return true;
+ if (pmu->alias_name &&
+ (strglobmatch(pmu->alias_name, pat) || !strcmp(pmu->alias_name, pat)))
+ return true;
+ if (pmu->name) {
+ size_t base_len = pmu_name_len_no_suffix(pmu->name);
+
+ if (strlen(pat) == base_len && !strncmp(pmu->name, pat, base_len))
+ return true;
+ }
+ return false;
+}
+
+static bool pmu_matches_filters(const struct perf_pmu *pmu, bool show_all,
+ int argc, const char **argv)
+{
+ size_t num_dups;
+
+ if (argc <= 0)
+ return true;
+
+ num_dups = show_all ? 1 : perf_pmu__num_duplicates(pmu);
+ for (int i = 0; i < argc; i++) {
+ const char *pat = argv[i];
+
+ if (pmu_one_matches_filter(pmu, pat))
+ return true;
+ if (num_dups > 1 && pmu->name && pmu->pmus) {
+ size_t base_len = pmu_name_len_no_suffix(pmu->name);
+ struct perf_pmu *dup = (struct perf_pmu *)pmu;
+
+ while ((dup = perf_pmus__scan(pmu->pmus, dup)) != NULL) {
+ if (!dup->name ||
+ pmu_name_len_no_suffix(dup->name) != base_len ||
+ strncmp(pmu->name, dup->name, base_len) != 0)
+ break;
+ if (pmu_one_matches_filter(dup, pat))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static void json_escape_fprintf(FILE *fp, struct strbuf *buf, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ strbuf_setlen(buf, 0);
+ for (size_t fmt_pos = 0; fmt_pos < strlen(fmt); fmt_pos++) {
+ switch (fmt[fmt_pos]) {
+ case '%':
+ fmt_pos++;
+ switch (fmt[fmt_pos]) {
+ case 's': {
+ const char *s = va_arg(args, const char*);
+
+ strbuf_addstr(buf, s);
+ break;
+ }
+ case 'S': {
+ const char *s = va_arg(args, const char*);
+
+ for (size_t s_pos = 0; s[s_pos] != '\0'; s_pos++) {
+ switch (s[s_pos]) {
+ case '\n':
+ strbuf_addstr(buf, "\\n");
+ break;
+ case '\r':
+ strbuf_addstr(buf, "\\r");
+ break;
+ case '\t':
+ strbuf_addstr(buf, "\\t");
+ break;
+ case '\\':
+ fallthrough;
+ case '\"':
+ strbuf_addch(buf, '\\');
+ fallthrough;
+ default:
+ strbuf_addch(buf, s[s_pos]);
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ strbuf_addch(buf, '%');
+ strbuf_addch(buf, fmt[fmt_pos]);
+ break;
+ }
+ break;
+ default:
+ strbuf_addch(buf, fmt[fmt_pos]);
+ break;
+ }
+ }
+ va_end(args);
+ fputs(buf->buf, fp);
+}
+
+struct print_format_state {
+ FILE *fp;
+ struct strbuf *buf;
+ bool json;
+ int count;
+};
+
+static int print_format_cb(void *vstate, const char *name, int config,
+ const unsigned long *bits)
+{
+ struct print_format_state *state = vstate;
+ char bits_buf[128];
+
+ if (!bits)
+ return 0;
+
+ bitmap_scnprintf((unsigned long *)bits, PERF_PMU_FORMAT_BITS,
+ bits_buf, sizeof(bits_buf));
+
+ if (state->json) {
+ char spec[160];
+
+ if (state->count == 0)
+ fprintf(state->fp, ",\n\t\"Formats\": {\n");
+
+ snprintf(spec, sizeof(spec), "%s:%s",
+ format_config_name(config), bits_buf);
+ json_escape_fprintf(state->fp, state->buf, "%s\t\t\"%S\": \"%S\"",
+ state->count > 0 ? ",\n" : "",
+ name, spec);
+ } else {
+ if (state->count == 0)
+ fprintf(state->fp, " Formats: ");
+
+ fprintf(state->fp, "%s%s (%s:%s)",
+ state->count > 0 ? ", " : "",
+ name, format_config_name(config), bits_buf);
+ }
+ state->count++;
+ return 0;
+}
+
+static bool evsel_matches_pmu(const struct evsel *evsel,
+ const struct perf_pmu *pmu, bool show_all)
+{
+ struct perf_pmu *ev_pmu = evsel__find_pmu(evsel);
+ const char *ev_pmu_name;
+
+ if (ev_pmu == pmu)
+ return true;
+
+ if (ev_pmu) {
+ if (!show_all && !pmu->is_core && pmu->name && ev_pmu->name) {
+ size_t base_len = pmu_name_len_no_suffix(pmu->name);
+
+ if (pmu_name_len_no_suffix(ev_pmu->name) == base_len &&
+ !strncmp(pmu->name, ev_pmu->name, base_len))
+ return true;
+ }
+ return false;
+ }
+
+ ev_pmu_name = evsel__pmu_name(evsel);
+ if (ev_pmu_name && pmu->name) {
+ if (!strcmp(ev_pmu_name, pmu->name))
+ return true;
+ if (!show_all && !pmu->is_core) {
+ size_t base_len = pmu_name_len_no_suffix(pmu->name);
+
+ if (pmu_name_len_no_suffix(ev_pmu_name) == base_len &&
+ !strncmp(pmu->name, ev_pmu_name, base_len))
+ return true;
+ }
+ }
+ return false;
+}
+
+static void print_recorded_events(FILE *fp, struct strbuf *buf,
+ const struct perf_pmu *pmu,
+ const struct perf_session *session,
+ bool show_all, bool json)
+{
+ struct strlist *ev_names;
+ struct str_node *pos;
+ struct evsel *evsel;
+ size_t count = 0;
+
+ if (!session || !session->evlist)
+ return;
+
+ ev_names = strlist__new(NULL, NULL);
+ if (!ev_names)
+ return;
+
+ evlist__for_each_entry(session->evlist, evsel) {
+ if (evsel_matches_pmu(evsel, pmu, show_all))
+ strlist__add(ev_names, evsel__name(evsel));
+ }
+
+ if (strlist__empty(ev_names)) {
+ strlist__delete(ev_names);
+ return;
+ }
+
+ if (json) {
+ fprintf(fp, ",\n\t\"RecordedEvents\": [");
+ strlist__for_each_entry(pos, ev_names) {
+ json_escape_fprintf(fp, buf, "%s\"%S\"",
+ count > 0 ? ", " : "", pos->s);
+ count++;
+ }
+ fprintf(fp, "]");
+ } else {
+ fprintf(fp, " Recorded events: ");
+ strlist__for_each_entry(pos, ev_names) {
+ fprintf(fp, "%s%s", count > 0 ? ", " : "", pos->s);
+ count++;
+ }
+ fputc('\n', fp);
+ }
+
+ strlist__delete(ev_names);
+}
+
+static const struct {
+ u64 bit;
+ const char *name;
+} sample_type_names[] = {
+ { PERF_SAMPLE_IP, "ip" },
+ { PERF_SAMPLE_TID, "tid" },
+ { PERF_SAMPLE_TIME, "time" },
+ { PERF_SAMPLE_ADDR, "addr" },
+ { PERF_SAMPLE_READ, "read" },
+ { PERF_SAMPLE_CALLCHAIN, "callchain" },
+ { PERF_SAMPLE_ID, "id" },
+ { PERF_SAMPLE_CPU, "cpu" },
+ { PERF_SAMPLE_PERIOD, "period" },
+ { PERF_SAMPLE_STREAM_ID, "stream_id" },
+ { PERF_SAMPLE_RAW, "raw" },
+ { PERF_SAMPLE_BRANCH_STACK, "branch_stack" },
+ { PERF_SAMPLE_REGS_USER, "regs_user" },
+ { PERF_SAMPLE_STACK_USER, "stack_user" },
+ { PERF_SAMPLE_WEIGHT, "weight" },
+ { PERF_SAMPLE_DATA_SRC, "data_src" },
+ { PERF_SAMPLE_IDENTIFIER, "identifier" },
+ { PERF_SAMPLE_TRANSACTION, "transaction" },
+ { PERF_SAMPLE_REGS_INTR, "regs_intr" },
+ { PERF_SAMPLE_PHYS_ADDR, "phys_addr" },
+ { PERF_SAMPLE_AUX, "aux" },
+ { PERF_SAMPLE_CGROUP, "cgroup" },
+ { PERF_SAMPLE_DATA_PAGE_SIZE, "data_page_size" },
+ { PERF_SAMPLE_CODE_PAGE_SIZE, "code_page_size" },
+ { PERF_SAMPLE_WEIGHT_STRUCT, "weight_struct" },
+};
+
+static const char *precise_level_desc(int lvl)
+{
+ switch (lvl) {
+ case 0:
+ return "0 - SAMPLE_IP can have arbitrary skid";
+ case 1:
+ return "1 - SAMPLE_IP must have constant skid";
+ case 2:
+ return "2 - SAMPLE_IP requested to have 0 skid";
+ case 3:
+ return "3 - SAMPLE_IP must have 0 skid, or uses randomization";
+ default:
+ return "unknown";
+ }
+}
+
+static void print_precise_levels(FILE *fp, const struct perf_pmu *pmu,
+ unsigned int precise_mask,
+ int max_precise, bool json)
+{
+ static const char * const pnames[] = { "", ":p", ":pp", ":ppp" };
+ bool need_sep = false;
+
+ if (json) {
+ fprintf(fp, ",\n\t\t\"PreciseLevels\": [");
+ for (int lvl = 1; lvl <= 3; lvl++) {
+ if (precise_mask & (1u << lvl)) {
+ fprintf(fp, "%s\"%s\"",
+ need_sep ? ", " : "", pnames[lvl]);
+ need_sep = true;
+ }
+ }
+ fprintf(fp, "]");
+ return;
+ }
+
+ fprintf(fp, " Precise sampling: ");
+ if (precise_mask == 0) {
+ if (perf_pmu__is_software(pmu)) {
+ color_fprintf(fp, PERF_COLOR_GREEN, "always precise");
+ fprintf(fp, " (software event, SAMPLE_IP has 0 skid)\n");
+ } else {
+ color_fprintf(fp, PERF_COLOR_RED, "not supported");
+ fprintf(fp, " (%s)\n", precise_level_desc(0));
+ }
+ return;
+ }
+
+ for (int lvl = 1; lvl <= 3; lvl++) {
+ if (precise_mask & (1u << lvl)) {
+ if (need_sep)
+ fprintf(fp, ", ");
+ color_fprintf(fp, PERF_COLOR_GREEN, "%s", pnames[lvl]);
+ fprintf(fp, " (%s)", precise_level_desc(lvl));
+ need_sep = true;
+ }
+ }
+ fprintf(fp, " (max_precise: ");
+ color_fprintf(fp, PERF_COLOR_GREEN, "%d", max_precise);
+ fprintf(fp, ")\n");
+}
+
+static void print_measured_skid(FILE *fp, const struct perf_pmu *pmu, bool json)
+{
+ static const char * const lvl_keys[] = { "0", ":p", ":pp", ":ppp" };
+ static const char * const lvl_labels[] = {
+ "level 0 (no :p)",
+ "level 1 (:p)",
+ "level 2 (:pp)",
+ "level 3 (:ppp)",
+ };
+ const typeof(pmu->probed) *p = &pmu->probed;
+ bool any = false;
+ bool first = true;
+
+ for (int lvl = 0; lvl <= 3; lvl++) {
+ if (p->skid[lvl].measured) {
+ any = true;
+ break;
+ }
+ }
+ if (!any)
+ return;
+
+ if (json)
+ fprintf(fp, ",\n\t\t\"MeasuredSkid\": {\n");
+ else
+ fprintf(fp, " Measured branch skid (instructions from branch):\n");
+
+ for (int lvl = 0; lvl <= 3; lvl++) {
+ if (!p->skid[lvl].measured)
+ continue;
+ if (json) {
+ fprintf(fp,
+ "%s\t\t\t\"%s\": { \"Samples\": %d, \"ExactIp\": %d, \"MinInsn\": %d, \"MaxInsn\": %d, \"AvgInsn\": %.1f }",
+ first ? "" : ",\n",
+ lvl_keys[lvl],
+ p->skid[lvl].samples,
+ p->skid[lvl].exact_samples,
+ p->skid[lvl].min_skid,
+ p->skid[lvl].max_skid,
+ p->skid[lvl].avg_skid);
+ } else {
+ const char *skid_color = PERF_COLOR_YELLOW;
+
+ if (p->skid[lvl].max_skid == 0)
+ skid_color = PERF_COLOR_GREEN;
+ else if (p->skid[lvl].max_skid <= 2)
+ skid_color = PERF_COLOR_CYAN;
+
+ fprintf(fp, " %-15s: ", lvl_labels[lvl]);
+ color_fprintf(fp, skid_color, "min %d, max %d, avg %.1f",
+ p->skid[lvl].min_skid,
+ p->skid[lvl].max_skid,
+ p->skid[lvl].avg_skid);
+ fprintf(fp, " (%d/%d exact_ip)\n",
+ p->skid[lvl].exact_samples,
+ p->skid[lvl].samples);
+ }
+ first = false;
+ }
+
+ if (json)
+ fprintf(fp, "\n\t\t}");
+}
+
+static void print_sample_type_list(FILE *fp, u64 mask, bool json)
+{
+ bool need_sep = false;
+
+ for (size_t x = 0; x < ARRAY_SIZE(sample_type_names); x++) {
+ if (!(mask & sample_type_names[x].bit))
+ continue;
+ if (json)
+ fprintf(fp, "%s\"%s\"", need_sep ? ", " : "", sample_type_names[x].name);
+ else
+ fprintf(fp, "%s%s", need_sep ? ", " : "", sample_type_names[x].name);
+ need_sep = true;
+ }
+ if (!json && !need_sep)
+ fprintf(fp, "none");
+}
+
+static void print_probed_regs(FILE *fp, struct strbuf *buf, const char *label,
+ u64 mask, int abi, u64 simd_mask, u64 pred_mask,
+ bool intr, bool json)
+{
+ const char *last_name = NULL;
+ bool need_sep = false;
+
+ if (!mask && !simd_mask && !pred_mask)
+ return;
+
+ if (json)
+ fprintf(fp, ",\n\t\t\"%s\": [", label);
+ else
+ fprintf(fp, " %s: ", label);
+
+ for (int reg = 0; reg < 64; reg++) {
+ const char *name;
+
+ if (!(mask & BIT_ULL(reg)))
+ continue;
+ name = perf_reg_name(reg, EM_HOST, EF_HOST, abi);
+ if (!name || !strcmp(name, "unknown"))
+ continue;
+ if (last_name && !strcmp(last_name, name))
+ continue;
+ last_name = name;
+
+ if (json) {
+ json_escape_fprintf(fp, buf, "%s\"%S\"",
+ need_sep ? ", " : "", name);
+ } else {
+ fprintf(fp, "%s%s", need_sep ? ", " : "", name);
+ }
+ need_sep = true;
+ }
+
+ for (int is_pred = 0; is_pred <= 1; is_pred++) {
+ bool pred = is_pred != 0;
+ u64 cmask = pred ? pred_mask : simd_mask;
+
+ for (int reg_c = 0; reg_c < 64; reg_c++) {
+ const char *cname;
+ u16 qwords = 0;
+ u64 bitmap;
+ char reg_range[64];
+
+ if (!(cmask & BIT_ULL(reg_c)))
+ continue;
+ cname = perf_simd_reg_class_name(EM_HOST, reg_c, pred);
+ bitmap = intr ?
+ perf_intr_simd_reg_class_bitmap_qwords(EM_HOST, reg_c,
+ &qwords, pred) :
+ perf_user_simd_reg_class_bitmap_qwords(EM_HOST, reg_c,
+ &qwords, pred);
+ if (!cname || !bitmap)
+ continue;
+ snprintf(reg_range, sizeof(reg_range), "%s0-%d",
+ cname, fls64(bitmap) - 1);
+ if (json) {
+ json_escape_fprintf(fp, buf, "%s\"%S\"",
+ need_sep ? ", " : "", reg_range);
+ } else {
+ fprintf(fp, "%s%s", need_sep ? ", " : "", reg_range);
+ }
+ need_sep = true;
+ }
+ }
+
+ if (json)
+ fprintf(fp, "]");
+ else
+ fputc('\n', fp);
+}
+
+static void pmu_event_counts(struct perf_pmu *pmu, size_t *out_total,
+ size_t *out_sysfs, size_t *out_json)
+{
+ size_t total = perf_pmu__num_events(pmu);
+ size_t sysfs;
+
+ if (pmu->type == PERF_TYPE_TRACEPOINT ||
+ (pmu->type >= PERF_PMU_TYPE_DRM_START &&
+ pmu->type <= PERF_PMU_TYPE_DRM_END) ||
+ (pmu->type >= PERF_PMU_TYPE_HWMON_START &&
+ pmu->type <= PERF_PMU_TYPE_HWMON_END))
+ sysfs = total;
+ else
+ sysfs = pmu->sysfs_aliases;
+
+ *out_total = total;
+ *out_sysfs = sysfs;
+ *out_json = total > sysfs ? total - sysfs : 0;
+}
+
+static void print_mem_events_static(FILE *fp, struct strbuf *buf,
+ const struct perf_pmu *pmu, bool json)
+{
+ bool first = true;
+
+ if (!pmu->mem_events)
+ return;
+
+ perf_pmu__mem_events_init_pmu((struct perf_pmu *)pmu);
+
+ for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
+ struct perf_mem_event *e = perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j);
+ char ev_buf[256];
+ const char *s;
+
+ if (!e || !e->tag || !e->supported)
+ continue;
+ s = perf_pmu__mem_events_name((struct perf_pmu *)pmu, j,
+ ev_buf, sizeof(ev_buf));
+ if (!s)
+ continue;
+
+ if (json) {
+ if (first)
+ fprintf(fp, ",\n\t\"MemoryEvents\": {\n");
+ json_escape_fprintf(fp, buf, "%s\t\t\"%S\": \"%S\"",
+ first ? "" : ",\n", e->tag, s);
+ } else {
+ if (first)
+ fprintf(fp, " Memory events: ");
+ fprintf(fp, "%s%s (%s)", first ? "" : ", ", e->tag, s);
+ }
+ first = false;
+ }
+
+ if (!first) {
+ if (json)
+ fprintf(fp, "\n\t}");
+ else
+ fputc('\n', fp);
+ }
+}
+
+static void print_pmu_json(FILE *fp, const struct perf_pmu *pmu,
+ const struct perf_session *session, bool show_all)
+{
+ struct strbuf buf;
+ size_t num_instances = show_all ? 1 : perf_pmu__num_duplicates(pmu);
+ char display_name[128];
+ size_t total_events, sysfs_events, json_events;
+ struct print_format_state format_state = {
+ .fp = fp,
+ .buf = &buf,
+ .json = true,
+ .count = 0,
+ };
+
+ strbuf_init(&buf, 0);
+
+ if (num_instances > 1) {
+ size_t base_len = pmu_name_len_no_suffix(pmu->name ?: "");
+
+ snprintf(display_name, sizeof(display_name), "%.*s",
+ (int)base_len, pmu->name ?: "");
+ } else {
+ snprintf(display_name, sizeof(display_name), "%s", pmu->name ?: "");
+ }
+
+ fprintf(fp, "{\n");
+ json_escape_fprintf(fp, &buf, "\t\"Unit\": \"%S\"", display_name);
+ if (pmu->name)
+ json_escape_fprintf(fp, &buf, ",\n\t\"FullName\": \"%S\"", pmu->name);
+ fprintf(fp, ",\n\t\"Instances\": %zu", num_instances);
+ if (pmu->alias_name)
+ json_escape_fprintf(fp, &buf, ",\n\t\"Alias\": \"%S\"", pmu->alias_name);
+ if (pmu->id)
+ json_escape_fprintf(fp, &buf, ",\n\t\"Identifier\": \"%S\"", pmu->id);
+ if (pmu->type != PERF_PMU_TYPE_FAKE) {
+ fprintf(fp, ",\n\t\"Type\": %u", pmu->type);
+ json_escape_fprintf(fp, &buf, ",\n\t\"TypeDesc\": \"%S\"",
+ pmu_type_desc(pmu->type));
+ }
+ fprintf(fp, ",\n\t\"IsCore\": %s", pmu->is_core ? "true" : "false");
+ fprintf(fp, ",\n\t\"IsUncore\": %s", pmu->is_uncore ? "true" : "false");
+ fprintf(fp, ",\n\t\"IsSoftware\": %s", perf_pmu__is_software(pmu) ? "true" : "false");
+ fprintf(fp, ",\n\t\"Auxtrace\": %s", pmu->auxtrace ? "true" : "false");
+ fprintf(fp, ",\n\t\"Selectable\": %s", pmu->selectable ? "true" : "false");
+
+ if (pmu->cpus) {
+ char cpus_buf[256];
+ int nr_cpus = perf_cpu_map__nr(pmu->cpus);
+
+ cpu_map__snprint(pmu->cpus, cpus_buf, sizeof(cpus_buf));
+ if (cpus_buf[0]) {
+ json_escape_fprintf(fp, &buf, ",\n\t\"CPUs\": \"%S\"", cpus_buf);
+ fprintf(fp, ",\n\t\"NrCPUs\": %d", nr_cpus);
+ }
+ }
+
+ perf_pmu__caps_parse((struct perf_pmu *)pmu);
+ if (!list_empty(&pmu->caps)) {
+ struct perf_pmu_caps *cap;
+ bool first = true;
+
+ fprintf(fp, ",\n\t\"Capabilities\": {\n");
+ list_for_each_entry(cap, &pmu->caps, list) {
+ json_escape_fprintf(fp, &buf, "%s\t\t\"%S\": \"%S\"",
+ first ? "" : ",\n",
+ cap->name, cap->value);
+ first = false;
+ }
+ fprintf(fp, "\n\t}");
+ }
+
+ perf_pmu__for_each_format((struct perf_pmu *)pmu, &format_state, print_format_cb);
+ if (format_state.count > 0)
+ fprintf(fp, "\n\t}");
+
+ pmu_event_counts((struct perf_pmu *)pmu, &total_events,
+ &sysfs_events, &json_events);
+ if (total_events > 0) {
+ fprintf(fp, ",\n\t\"Events\": {\n");
+ fprintf(fp, "\t\t\"Total\": %zu,\n", total_events);
+ fprintf(fp, "\t\t\"Sysfs\": %zu,\n", sysfs_events);
+ fprintf(fp, "\t\t\"Json\": %zu\n", json_events);
+ fprintf(fp, "\t}");
+ }
+
+ print_mem_events_static(fp, &buf, pmu, /*json=*/true);
+ print_recorded_events(fp, &buf, pmu, session, show_all, /*json=*/true);
+
+ if (pmu->probed.checked) {
+ const typeof(pmu->probed) *p = &pmu->probed;
+
+ fprintf(fp, ",\n\t\"Probed\": {\n");
+ fprintf(fp, "\t\t\"OpenSupported\": %s",
+ p->open_supported ? "true" : "false");
+ if (p->open_supported) {
+ bool need_sep = false;
+ const struct {
+ bool supported;
+ const char *name;
+ } excl[] = {
+ { p->exclude_user, "exclude_user" },
+ { p->exclude_kernel, "exclude_kernel" },
+ { p->exclude_hv, "exclude_hv" },
+ { p->exclude_idle, "exclude_idle" },
+ { p->exclude_host, "exclude_host" },
+ { p->exclude_guest, "exclude_guest" },
+ };
+
+ if (p->probe_event) {
+ json_escape_fprintf(fp, &buf,
+ ",\n\t\t\"ProbeEvent\": \"%S\"",
+ p->probe_event);
+ }
+ fprintf(fp, ",\n\t\t\"Counting\": %s",
+ p->can_count ? "true" : "false");
+ fprintf(fp, ",\n\t\t\"Sampling\": %s",
+ p->can_sample ? "true" : "false");
+ fprintf(fp, ",\n\t\t\"BranchStack\": %s",
+ p->can_branch_stack ? "true" : "false");
+ if (p->can_sample) {
+ if (!pmu->auxtrace) {
+ const char *pdesc = (p->max_precise == 0 &&
+ perf_pmu__is_software(pmu)) ?
+ "software event - SAMPLE_IP has 0 skid" :
+ precise_level_desc(p->max_precise);
+
+ fprintf(fp, ",\n\t\t\"MaxPrecise\": %d", p->max_precise);
+ json_escape_fprintf(fp, &buf,
+ ",\n\t\t\"MaxPreciseDesc\": \"%S\"",
+ pdesc);
+ print_precise_levels(fp, pmu, p->precise_mask,
+ p->max_precise, /*json=*/true);
+
+ print_measured_skid(fp, pmu, /*json=*/true);
+ }
+
+ fprintf(fp, ",\n\t\t\"SampleTypes\": [");
+ print_sample_type_list(fp, p->sample_types, /*json=*/true);
+ fprintf(fp, "]");
+ }
+
+ print_probed_regs(fp, &buf, "UserRegisters",
+ p->user_regs_mask, p->user_regs_abi,
+ p->user_simd_mask, p->user_pred_mask,
+ /*intr=*/false, /*json=*/true);
+ print_probed_regs(fp, &buf, "IntrRegisters",
+ p->intr_regs_mask & ~p->intr_regs_precise_mask,
+ p->intr_regs_abi,
+ p->intr_simd_mask & ~p->intr_simd_precise_mask,
+ p->intr_pred_mask & ~p->intr_pred_precise_mask,
+ /*intr=*/true, /*json=*/true);
+ print_probed_regs(fp, &buf, "IntrRegistersPreciseOnly",
+ p->intr_regs_precise_mask, p->intr_regs_abi,
+ p->intr_simd_precise_mask, p->intr_pred_precise_mask,
+ /*intr=*/true, /*json=*/true);
+
+ if (p->mem_events_mask) {
+ need_sep = false;
+ fprintf(fp, ",\n\t\t\"MemoryEvents\": [");
+ for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
+ struct perf_mem_event *e =
+ perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j);
+
+ if (e && e->tag && (p->mem_events_mask & (1u << j))) {
+ json_escape_fprintf(fp, &buf, "%s\"%S\"",
+ need_sep ? ", " : "",
+ e->tag);
+ need_sep = true;
+ }
+ }
+ fprintf(fp, "]");
+
+ fprintf(fp, ",\n\t\t\"MemorySampleTypes\": [");
+ print_sample_type_list(fp, p->mem_sample_types, /*json=*/true);
+ fprintf(fp, "]");
+ }
+
+ need_sep = false;
+ fprintf(fp, ",\n\t\t\"ExcludeModifiers\": [");
+ for (size_t x = 0; x < ARRAY_SIZE(excl); x++) {
+ if (excl[x].supported) {
+ fprintf(fp, "%s\"%s\"",
+ need_sep ? ", " : "",
+ excl[x].name);
+ need_sep = true;
+ }
+ }
+ fprintf(fp, "]");
+
+ {
+ const struct {
+ bool supported;
+ const char *name;
+ } feats[] = {
+ { p->can_leader_sampling, "leader_sampling" },
+ { p->can_sw_grouping, "sw_grouping" },
+ { p->can_inherit, "inherit" },
+ { p->can_freq, "sample_freq" },
+ { p->can_write_backward, "write_backward" },
+ { p->can_callchain, "callchain" },
+ { p->can_data_src, "data_src" },
+ { p->can_weight, "weight" },
+ { p->can_pinned, "pinned" },
+ { p->can_exclusive, "exclusive" },
+ { p->can_format_lost, "format_lost" },
+ { p->can_acr, "auto_counter_reload" },
+ { p->can_aux_start_paused, "aux_start_paused" },
+ { p->can_aux_pause, "aux_pause" },
+ { p->can_aux_resume, "aux_resume" },
+ { p->can_aux_sample, "aux_sample" },
+ { p->can_user_rdpmc, "user_rdpmc" },
+ { p->can_user_time, "user_time" },
+ { p->can_user_time_zero, "user_time_zero" },
+ { p->can_user_time_short, "user_time_short" },
+ };
+
+ need_sep = false;
+ fprintf(fp, ",\n\t\t\"Features\": [");
+ for (size_t x = 0; x < ARRAY_SIZE(feats); x++) {
+ if (feats[x].supported) {
+ fprintf(fp, "%s\"%s\"",
+ need_sep ? ", " : "",
+ feats[x].name);
+ need_sep = true;
+ }
+ }
+ fprintf(fp, "]");
+ }
+
+ if (pmu->auxtrace) {
+ const struct {
+ bool supported;
+ const char *name;
+ } aux_acts[] = {
+ { p->can_aux_start_paused, "aux_start_paused" },
+ { p->can_aux_pause, "aux_pause" },
+ { p->can_aux_resume, "aux_resume" },
+ { p->can_aux_sample, "aux_sample" },
+ };
+
+ need_sep = false;
+ fprintf(fp, ",\n\t\t\"AuxActions\": [");
+ for (size_t x = 0; x < ARRAY_SIZE(aux_acts); x++) {
+ if (aux_acts[x].supported) {
+ fprintf(fp, "%s\"%s\"",
+ need_sep ? ", " : "",
+ aux_acts[x].name);
+ need_sep = true;
+ }
+ }
+ fprintf(fp, "]");
+ }
+
+ if (p->can_user_rdpmc && p->pmc_width > 0)
+ fprintf(fp, ",\n\t\t\"PmcWidth\": %u", p->pmc_width);
+
+ fprintf(fp, ",\n\t\t\"UserRdpmc\": {\n");
+ fprintf(fp, "\t\t\t\"Available\": %s,\n",
+ p->rdpmc_available ? "true" : "false");
+ fprintf(fp, "\t\t\t\"Enabled\": %s,\n",
+ p->can_user_rdpmc ? "true" : "false");
+ fprintf(fp, "\t\t\t\"Working\": %s",
+ p->rdpmc_works ? "true" : "false");
+ if (p->rdpmc_available && p->pmc_width > 0)
+ fprintf(fp, ",\n\t\t\t\"PmcWidth\": %u", p->pmc_width);
+ if (p->rdpmc_sysfs >= 0)
+ fprintf(fp, ",\n\t\t\t\"Sysfs\": %d", p->rdpmc_sysfs);
+ fprintf(fp, "\n\t\t}");
+
+ fprintf(fp, ",\n\t\t\"MaxGroupSize\": %d",
+ p->max_group_size);
+ fprintf(fp, ",\n\t\t\"MaxGroupSizeCapped\": %s",
+ p->max_group_capped ? "true" : "false");
+ if (p->max_gp_group_size > 0) {
+ fprintf(fp, ",\n\t\t\"MaxGpGroupSize\": %d",
+ p->max_gp_group_size);
+ fprintf(fp, ",\n\t\t\"MaxGpGroupSizeCapped\": %s",
+ p->max_gp_group_capped ? "true" : "false");
+ }
+ } else {
+ json_escape_fprintf(fp, &buf, ",\n\t\t\"Error\": \"%S\"",
+ strerror(p->open_err));
+ }
+ fprintf(fp, "\n\t}");
+ }
+
+ fprintf(fp, "\n}");
+ strbuf_release(&buf);
+}
+
+static void print_pmu_text(FILE *fp, const struct perf_pmu *pmu,
+ const struct perf_session *session, bool show_all)
+{
+ size_t num_instances = show_all ? 1 : perf_pmu__num_duplicates(pmu);
+ char display_name[128];
+ const char *kind_sep = "";
+ size_t total_events, sysfs_events, json_events;
+ struct print_format_state format_state = {
+ .fp = fp,
+ .buf = NULL,
+ .json = false,
+ .count = 0,
+ };
+
+ if (num_instances > 1) {
+ size_t base_len = pmu_name_len_no_suffix(pmu->name ?: "");
+
+ snprintf(display_name, sizeof(display_name), "%.*s",
+ (int)base_len, pmu->name ?: "");
+ } else {
+ snprintf(display_name, sizeof(display_name), "%s", pmu->name ?: "");
+ }
+
+ color_fprintf(fp, PERF_COLOR_BOLD, "%s", display_name);
+ fprintf(fp, " (");
+ if (pmu->type != PERF_PMU_TYPE_FAKE) {
+ fprintf(fp, "type: %u [%s]", pmu->type, pmu_type_desc(pmu->type));
+ kind_sep = ", ";
+ }
+ if (pmu->is_core) {
+ fprintf(fp, "%s", kind_sep);
+ color_fprintf(fp, PERF_COLOR_GREEN, "core");
+ kind_sep = ", ";
+ }
+ if (pmu->is_uncore) {
+ fprintf(fp, "%s", kind_sep);
+ color_fprintf(fp, PERF_COLOR_CYAN, "uncore");
+ kind_sep = ", ";
+ }
+ if (perf_pmu__is_software(pmu)) {
+ fprintf(fp, "%s", kind_sep);
+ color_fprintf(fp, PERF_COLOR_BLUE, "software");
+ kind_sep = ", ";
+ }
+ if (pmu->auxtrace) {
+ fprintf(fp, "%s", kind_sep);
+ color_fprintf(fp, PERF_COLOR_MAGENTA, "auxtrace");
+ kind_sep = ", ";
+ }
+ if (pmu->selectable) {
+ fprintf(fp, "%sselectable", kind_sep);
+ kind_sep = ", ";
+ }
+ fprintf(fp, "):\n");
+
+ if (num_instances > 1 || (pmu->name && strcmp(pmu->name, display_name) != 0))
+ fprintf(fp, " Instances: %zu (first: %s)\n", num_instances, pmu->name);
+ if (pmu->alias_name)
+ fprintf(fp, " Alias: %s\n", pmu->alias_name);
+ if (pmu->id)
+ fprintf(fp, " Identifier: %s\n", pmu->id);
+ if (pmu->cpus) {
+ char cpus_buf[256];
+ int nr_cpus = perf_cpu_map__nr(pmu->cpus);
+
+ cpu_map__snprint(pmu->cpus, cpus_buf, sizeof(cpus_buf));
+ if (cpus_buf[0]) {
+ if (nr_cpus > 0)
+ fprintf(fp, " CPUs: %s (%d %s)\n",
+ cpus_buf, nr_cpus,
+ nr_cpus == 1 ? "CPU" : "CPUs");
+ else
+ fprintf(fp, " CPUs: %s\n", cpus_buf);
+ }
+ }
+
+ perf_pmu__caps_parse((struct perf_pmu *)pmu);
+ if (!list_empty(&pmu->caps)) {
+ struct perf_pmu_caps *cap;
+ bool first = true;
+
+ fprintf(fp, " Capabilities: ");
+ list_for_each_entry(cap, &pmu->caps, list) {
+ fprintf(fp, "%s%s=%s", first ? "" : ", ", cap->name, cap->value);
+ first = false;
+ }
+ fputc('\n', fp);
+ }
+
+ perf_pmu__for_each_format((struct perf_pmu *)pmu, &format_state, print_format_cb);
+ if (format_state.count > 0)
+ fputc('\n', fp);
+
+ pmu_event_counts((struct perf_pmu *)pmu, &total_events,
+ &sysfs_events, &json_events);
+ if (total_events > 0) {
+ fprintf(fp, " Events: %zu total (%zu sysfs, %zu json)\n",
+ total_events, sysfs_events, json_events);
+ }
+
+ print_mem_events_static(fp, NULL, pmu, /*json=*/false);
+ print_recorded_events(fp, NULL, pmu, session, show_all, /*json=*/false);
+
+ if (pmu->probed.checked) {
+ const typeof(pmu->probed) *p = &pmu->probed;
+
+ if (!p->open_supported) {
+ fprintf(fp, " Probed capabilities: ");
+ if (p->open_err == EACCES && pmu->name &&
+ !strcmp(pmu->name, "kprobe")) {
+ color_fprintf(fp, PERF_COLOR_YELLOW,
+ "unavailable (%s; requires CAP_PERFMON or CAP_SYS_ADMIN)",
+ strerror(p->open_err));
+ } else if (p->open_err == EACCES && pmu->name &&
+ !strcmp(pmu->name, "uprobe")) {
+ color_fprintf(fp, PERF_COLOR_YELLOW,
+ "unavailable (%s; requires CAP_SYS_ADMIN)",
+ strerror(p->open_err));
+ } else {
+ color_fprintf(fp, PERF_COLOR_YELLOW, "unavailable (%s)",
+ strerror(p->open_err));
+ }
+ fputc('\n', fp);
+ } else {
+ const char *sep = "";
+ const struct {
+ bool supported;
+ const char *name;
+ } excl[] = {
+ { p->exclude_user, "exclude_user" },
+ { p->exclude_kernel, "exclude_kernel" },
+ { p->exclude_hv, "exclude_hv" },
+ { p->exclude_idle, "exclude_idle" },
+ { p->exclude_host, "exclude_host" },
+ { p->exclude_guest, "exclude_guest" },
+ };
+
+ fprintf(fp, " Probed capabilities (event: ");
+ color_fprintf(fp, PERF_COLOR_CYAN, "%s",
+ p->probe_event ?: "default");
+ fprintf(fp, "):\n");
+ fprintf(fp, " Modes: ");
+ if (p->can_count) {
+ fprintf(fp, "%s", sep);
+ color_fprintf(fp, PERF_COLOR_GREEN, "counting");
+ sep = ", ";
+ }
+ if (p->can_sample) {
+ fprintf(fp, "%s", sep);
+ color_fprintf(fp, PERF_COLOR_GREEN, "sampling");
+ sep = ", ";
+ }
+ if (p->can_branch_stack) {
+ fprintf(fp, "%s", sep);
+ color_fprintf(fp, PERF_COLOR_GREEN, "branch_stack");
+ sep = ", ";
+ }
+ if (*sep == '\0')
+ color_fprintf(fp, PERF_COLOR_RED, "none");
+ fputc('\n', fp);
+
+ if (p->can_sample) {
+ if (!pmu->auxtrace) {
+ print_precise_levels(fp, pmu, p->precise_mask,
+ p->max_precise, /*json=*/false);
+
+ print_measured_skid(fp, pmu, /*json=*/false);
+ }
+
+ fprintf(fp, " Sample types: ");
+ print_sample_type_list(fp, p->sample_types, /*json=*/false);
+ fputc('\n', fp);
+ }
+
+ print_probed_regs(fp, NULL, "User registers",
+ p->user_regs_mask, p->user_regs_abi,
+ p->user_simd_mask, p->user_pred_mask,
+ /*intr=*/false, /*json=*/false);
+ print_probed_regs(fp, NULL, "Intr registers",
+ p->intr_regs_mask & ~p->intr_regs_precise_mask,
+ p->intr_regs_abi,
+ p->intr_simd_mask & ~p->intr_simd_precise_mask,
+ p->intr_pred_mask & ~p->intr_pred_precise_mask,
+ /*intr=*/true, /*json=*/false);
+ print_probed_regs(fp, NULL, "Intr registers (precise sampling only)",
+ p->intr_regs_precise_mask, p->intr_regs_abi,
+ p->intr_simd_precise_mask, p->intr_pred_precise_mask,
+ /*intr=*/true, /*json=*/false);
+
+ if (p->mem_events_mask) {
+ sep = "";
+ fprintf(fp, " Memory events (probed): ");
+ for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
+ struct perf_mem_event *e =
+ perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j);
+
+ if (e && e->tag && (p->mem_events_mask & (1u << j))) {
+ fprintf(fp, "%s%s", sep, e->tag);
+ sep = ", ";
+ }
+ }
+ if (p->mem_sample_types) {
+ fprintf(fp, " (sample types: ");
+ print_sample_type_list(fp, p->mem_sample_types,
+ /*json=*/false);
+ fprintf(fp, ")");
+ }
+ fputc('\n', fp);
+ }
+
+ sep = "";
+ fprintf(fp, " Exclusion modifiers: ");
+ for (size_t x = 0; x < ARRAY_SIZE(excl); x++) {
+ if (excl[x].supported) {
+ fprintf(fp, "%s%s", sep, excl[x].name);
+ sep = ", ";
+ }
+ }
+ if (*sep == '\0')
+ fprintf(fp, "none");
+ fputc('\n', fp);
+
+ {
+ const struct {
+ bool supported;
+ const char *name;
+ } feats[] = {
+ { p->can_leader_sampling, "leader_sampling" },
+ { p->can_sw_grouping, "sw_grouping" },
+ { p->can_inherit, "inherit" },
+ { p->can_freq, "sample_freq" },
+ { p->can_write_backward, "write_backward" },
+ { p->can_callchain, "callchain" },
+ { p->can_data_src, "data_src" },
+ { p->can_weight, "weight" },
+ { p->can_pinned, "pinned" },
+ { p->can_exclusive, "exclusive" },
+ { p->can_format_lost, "format_lost" },
+ { p->can_acr, "auto_counter_reload" },
+ { p->can_aux_start_paused, "aux_start_paused" },
+ { p->can_aux_pause, "aux_pause" },
+ { p->can_aux_resume, "aux_resume" },
+ { p->can_aux_sample, "aux_sample" },
+ { p->can_user_rdpmc, "user_rdpmc" },
+ { p->can_user_time, "user_time" },
+ { p->can_user_time_zero, "user_time_zero" },
+ { p->can_user_time_short, "user_time_short" },
+ };
+
+ sep = "";
+ fprintf(fp, " Features: ");
+ for (size_t x = 0; x < ARRAY_SIZE(feats); x++) {
+ if (feats[x].supported) {
+ fprintf(fp, "%s%s", sep, feats[x].name);
+ sep = ", ";
+ }
+ }
+ if (*sep == '\0')
+ fprintf(fp, "none");
+ fputc('\n', fp);
+ }
+
+ if (pmu->auxtrace) {
+ const struct {
+ bool supported;
+ const char *name;
+ } aux_acts[] = {
+ { p->can_aux_start_paused, "aux_start_paused" },
+ { p->can_aux_pause, "aux_pause" },
+ { p->can_aux_resume, "aux_resume" },
+ { p->can_aux_sample, "aux_sample" },
+ };
+
+ sep = "";
+ fprintf(fp, " AUX actions: ");
+ for (size_t x = 0; x < ARRAY_SIZE(aux_acts); x++) {
+ if (aux_acts[x].supported) {
+ fprintf(fp, "%s%s", sep, aux_acts[x].name);
+ sep = ", ";
+ }
+ }
+ if (*sep == '\0')
+ fprintf(fp, "none");
+ fputc('\n', fp);
+ }
+
+ if (pmu->is_core || p->rdpmc_available) {
+ fprintf(fp, " User space counter reading (rdpmc): ");
+ if (!p->rdpmc_available) {
+ color_fprintf(fp, PERF_COLOR_RED, "not supported");
+ fputc('\n', fp);
+ } else {
+ color_fprintf(fp, PERF_COLOR_GREEN, "available");
+ fprintf(fp, ", ");
+ color_fprintf(fp,
+ p->can_user_rdpmc ?
+ PERF_COLOR_GREEN : PERF_COLOR_YELLOW,
+ "%s",
+ p->can_user_rdpmc ? "enabled" : "disabled");
+ if (p->rdpmc_sysfs >= 0)
+ fprintf(fp, " (sysfs: %d)", p->rdpmc_sysfs);
+ if (p->can_user_rdpmc) {
+ fprintf(fp, ", ");
+ color_fprintf(fp,
+ p->rdpmc_works ?
+ PERF_COLOR_GREEN : PERF_COLOR_RED,
+ "%s",
+ p->rdpmc_works ?
+ "working" : "read failed");
+ }
+ if (p->pmc_width > 0)
+ fprintf(fp, " (width: %u bits)", p->pmc_width);
+ fputc('\n', fp);
+ }
+ }
+
+ fprintf(fp, " Max event group size: %s%d\n",
+ p->max_group_capped ? ">=" : "",
+ p->max_group_size);
+ if (p->max_gp_group_size > 0 &&
+ p->max_gp_group_size != p->max_group_size) {
+ fprintf(fp, " Max GP event group size: %s%d\n",
+ p->max_gp_group_capped ? ">=" : "",
+ p->max_gp_group_size);
+ }
+ }
+ }
+}
+
+static int process_sample_stop(const struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ session_done = 1;
+ return 0;
+}
+
+#define MAX_SUMMARY_METRICS 2048
+#define MAX_DRILLDOWN_LEVELS 16
+
+struct summary_metric_item {
+ char *pmu;
+ char *name;
+ char *groups;
+ int level;
+ bool has_children;
+ bool has_parent;
+};
+
+struct summary_metrics_state {
+ struct strlist *metrics;
+ struct strlist *groups;
+ struct summary_metric_item items[MAX_SUMMARY_METRICS];
+ size_t nr_items;
+};
+
+static bool group_list_has_token(const char *groups, const char *token)
+{
+ size_t tok_len;
+ const char *p;
+
+ if (!groups || !token || !*token)
+ return false;
+ tok_len = strlen(token);
+ p = groups;
+ while (*p) {
+ const char *semi = strchr(p, ';');
+ size_t len = semi ? (size_t)(semi - p) : strlen(p);
+
+ if (len == tok_len && !strncmp(p, token, len))
+ return true;
+ if (!semi)
+ break;
+ p = semi + 1;
+ }
+ return false;
+}
+
+static int collect_summary_metric_cb(const struct pmu_metric *pm,
+ const struct pmu_metrics_table *table __maybe_unused,
+ void *vdata)
+{
+ struct summary_metrics_state *st = vdata;
+
+ if (pm->metric_name)
+ strlist__add(st->metrics, pm->metric_name);
+
+ if (pm->metric_group) {
+ char *copy = strdup(pm->metric_group);
+ char *tok, *saveptr = NULL;
+
+ if (copy) {
+ for (tok = strtok_r(copy, ";", &saveptr); tok;
+ tok = strtok_r(NULL, ";", &saveptr)) {
+ if (*tok)
+ strlist__add(st->groups, tok);
+ }
+ free(copy);
+ }
+ }
+
+ if (pm->metric_name) {
+ const char *pmu_name = pm->pmu ?: "cpu";
+ struct summary_metric_item *existing = NULL;
+
+ for (size_t i = 0; i < st->nr_items; i++) {
+ if (st->items[i].pmu && st->items[i].name &&
+ !strcmp(st->items[i].pmu, pmu_name) &&
+ !strcmp(st->items[i].name, pm->metric_name)) {
+ existing = &st->items[i];
+ break;
+ }
+ }
+ if (!existing && st->nr_items < MAX_SUMMARY_METRICS) {
+ existing = &st->items[st->nr_items++];
+ existing->pmu = strdup(pmu_name);
+ existing->name = strdup(pm->metric_name);
+ existing->groups = strdup(pm->metric_group ?: "");
+ } else if (existing && pm->metric_group && existing->groups &&
+ !strstr(existing->groups, pm->metric_group)) {
+ char *merged = NULL;
+
+ if (asprintf(&merged, "%s;%s", existing->groups, pm->metric_group) > 0) {
+ free(existing->groups);
+ existing->groups = merged;
+ }
+ }
+ }
+ return 0;
+}
+
+static int compute_metric_drilldown(struct summary_metrics_state *st,
+ int level_counts[MAX_DRILLDOWN_LEVELS],
+ char *level_labels[MAX_DRILLDOWN_LEVELS])
+{
+ int max_level = 0;
+
+ memset(level_counts, 0, sizeof(int) * MAX_DRILLDOWN_LEVELS);
+ memset(level_labels, 0, sizeof(char *) * MAX_DRILLDOWN_LEVELS);
+
+ for (size_t i = 0; i < st->nr_items; i++) {
+ char child_grp[256];
+
+ if (!st->items[i].name || !st->items[i].pmu)
+ continue;
+ snprintf(child_grp, sizeof(child_grp), "%s_group", st->items[i].name);
+ if (!strlist__has_entry(st->groups, child_grp))
+ continue;
+
+ for (size_t j = 0; j < st->nr_items; j++) {
+ if (i == j || !st->items[j].groups || !st->items[j].pmu)
+ continue;
+ if (strcmp(st->items[i].pmu, st->items[j].pmu) != 0)
+ continue;
+ if (group_list_has_token(st->items[j].groups, child_grp)) {
+ st->items[i].has_children = true;
+ st->items[j].has_parent = true;
+ }
+ }
+ }
+
+ for (size_t i = 0; i < st->nr_items; i++) {
+ if (st->items[i].has_children && !st->items[i].has_parent) {
+ st->items[i].level = 1;
+ max_level = 1;
+ }
+ }
+
+ for (int d = 1; d < MAX_DRILLDOWN_LEVELS - 1; d++) {
+ bool progress = false;
+
+ for (size_t i = 0; i < st->nr_items; i++) {
+ char child_grp[256];
+
+ if (st->items[i].level != d || !st->items[i].has_children)
+ continue;
+ snprintf(child_grp, sizeof(child_grp), "%s_group", st->items[i].name);
+ for (size_t j = 0; j < st->nr_items; j++) {
+ if (i == j || !st->items[j].groups || !st->items[j].pmu)
+ continue;
+ if (strcmp(st->items[i].pmu, st->items[j].pmu) != 0)
+ continue;
+ if (group_list_has_token(st->items[j].groups, child_grp) &&
+ st->items[j].level < d + 1) {
+ st->items[j].level = d + 1;
+ if (d + 1 > max_level)
+ max_level = d + 1;
+ progress = true;
+ }
+ }
+ }
+ if (!progress)
+ break;
+ }
+
+ for (int d = 1; d <= max_level; d++) {
+ struct str_node *pos;
+ const char *best_label = NULL;
+ int best_hits = 0;
+ int total_at_d = 0;
+
+ for (size_t i = 0; i < st->nr_items; i++) {
+ if (st->items[i].level == d)
+ total_at_d++;
+ }
+ level_counts[d] = total_at_d;
+
+ strlist__for_each_entry(pos, st->groups) {
+ const char *g = pos->s;
+ size_t glen = strlen(g);
+ int hits_in_d = 0;
+ bool in_other_d = false;
+
+ if (!strcasecmp(g, "Default") || !strcasecmp(g, "Summary") ||
+ (glen > 6 && !strcmp(g + glen - 6, "_group")))
+ continue;
+
+ for (size_t i = 0; i < st->nr_items; i++) {
+ if (!st->items[i].groups || st->items[i].level == 0)
+ continue;
+ if (group_list_has_token(st->items[i].groups, g)) {
+ if (st->items[i].level == d)
+ hits_in_d++;
+ else
+ in_other_d = true;
+ }
+ }
+
+ if (!in_other_d &&
+ (hits_in_d > best_hits ||
+ (hits_in_d == best_hits && best_label &&
+ strlen(g) > strlen(best_label)))) {
+ best_hits = hits_in_d;
+ best_label = g;
+ }
+ }
+
+ if (best_label && best_hits * 2 >= total_at_d)
+ level_labels[d] = strdup(best_label);
+ }
+
+ return max_level;
+}
+
+static bool is_mem_bw_pmu_name(const char *name)
+{
+ if (!name)
+ return false;
+ return strstarts(name, "uncore_imc") ||
+ strstarts(name, "uncore_mc") ||
+ strstarts(name, "uncore_edc") ||
+ strstarts(name, "uncore_mchbm") ||
+ strstarts(name, "amd_df") ||
+ strstarts(name, "amd_umc") ||
+ strstarts(name, "arm_dmc") ||
+ strstarts(name, "dmc620") ||
+ strstarts(name, "arm_cmn") ||
+ strstr(name, "ddrc") != NULL ||
+ strstr(name, "ddr") != NULL ||
+ !strcmp(name, "hv_24x7") ||
+ strstarts(name, "nest_mc");
+}
+
+static bool is_io_bw_pmu_name(const char *name)
+{
+ if (!name)
+ return false;
+ return strstarts(name, "uncore_iio") ||
+ strstarts(name, "uncore_irp") ||
+ strstarts(name, "uncore_pcie") ||
+ strstarts(name, "uncore_upi") ||
+ strstarts(name, "uncore_qpi") ||
+ strstarts(name, "uncore_m2pcie") ||
+ strstarts(name, "uncore_m3upi") ||
+ strstarts(name, "uncore_arb") ||
+ strstarts(name, "cxl") ||
+ strstarts(name, "amd_iommu") ||
+ strstarts(name, "amd_xgmi") ||
+ strstarts(name, "arm_smmu") ||
+ strstr(name, "pcie") != NULL ||
+ strstarts(name, "dwc_rootport") ||
+ strstarts(name, "cci") ||
+ strstarts(name, "ccn");
+}
+
+static void print_strlist_color(FILE *fp, const char *color, struct strlist *list)
+{
+ struct str_node *pos;
+ bool first = true;
+
+ strlist__for_each_entry(pos, list) {
+ if (!first)
+ fprintf(fp, ", ");
+ color_fprintf(fp, color, "%s", pos->s);
+ first = false;
+ }
+}
+
+static void add_pmu_display_name(struct strlist *list, const struct perf_pmu *pmu)
+{
+ size_t num_inst = perf_pmu__num_duplicates(pmu);
+ char buf[128];
+
+ if (!pmu->name)
+ return;
+ if (num_inst > 1) {
+ size_t base_len = pmu_name_len_no_suffix(pmu->name);
+
+ snprintf(buf, sizeof(buf), "%.*s", (int)base_len, pmu->name);
+ strlist__add(list, buf);
+ } else {
+ strlist__add(list, pmu->name);
+ }
+}
+
+static void print_system_summary(FILE *fp, const struct perf_session *session,
+ bool probed, bool leading_newline)
+{
+ struct perf_env *env = session ? (struct perf_env *)&session->header.env : host_env();
+ /* As in cmd_sysinfo(), use the PMUs read from the perf.data file. */
+ struct perf_pmus *pmus = session ? &env->pmus : perf_env__pmus(env);
+ const struct pmu_metrics_table *mtable;
+ struct perf_pmu *pmu = NULL;
+ struct strlist *core_pmus = strlist__new(NULL, NULL);
+ struct strlist *mem_bw_pmus = strlist__new(NULL, NULL);
+ struct strlist *io_bw_pmus = strlist__new(NULL, NULL);
+ struct strlist *aux_pmus = strlist__new(NULL, NULL);
+ struct strlist *missing = strlist__new(NULL, NULL);
+ struct summary_metrics_state *mstate = zalloc(sizeof(*mstate));
+ int level_counts[MAX_DRILLDOWN_LEVELS];
+ char *level_labels[MAX_DRILLDOWN_LEVELS];
+ int max_drill_lvl = 0;
+ bool has_sw_sampling = false;
+ bool core_probed = false;
+ bool core_can_sample = false;
+ bool core_precise = false;
+ int max_precise = 0;
+ bool core_rdpmc = false;
+ bool core_branch_stack = false;
+ int max_br_depth = 0;
+ bool core_mem_events = false;
+ bool core_leader_sampling = false;
+ size_t total_pmus = 0, total_events = 0, total_sysfs_events = 0, total_json_events = 0;
+ size_t nr_core, nr_metrics, nr_groups;
+
+ if (!mstate)
+ goto out_free;
+ mstate->metrics = strlist__new(NULL, NULL);
+ mstate->groups = strlist__new(NULL, NULL);
+
+ if (!core_pmus || !mem_bw_pmus || !io_bw_pmus || !aux_pmus ||
+ !missing || !mstate->metrics || !mstate->groups)
+ goto out_free;
+
+ while ((pmu = perf_pmus__scan_skip_duplicates(pmus, pmu)) != NULL) {
+ size_t pmu_events, sysfs_ev, json_ev;
+
+ pmu_event_counts(pmu, &pmu_events, &sysfs_ev, &json_ev);
+
+ total_pmus++;
+ total_events += pmu_events;
+ total_sysfs_events += sysfs_ev;
+ total_json_events += json_ev;
+
+ if (pmu->is_core &&
+ (pmu->from_env || sysfs_ev > 0 || !list_empty(&pmu->format) ||
+ (probed && pmu->probed.checked && pmu->probed.open_supported))) {
+ struct perf_pmu_caps *br_cap;
+
+ add_pmu_display_name(core_pmus, pmu);
+ perf_pmu__caps_parse(pmu);
+ br_cap = perf_pmu__get_cap(pmu, "branches");
+ if (br_cap && br_cap->value) {
+ int depth = atoi(br_cap->value);
+
+ if (depth > max_br_depth)
+ max_br_depth = depth;
+ }
+ if (probed && pmu->probed.checked) {
+ core_probed = true;
+ if (pmu->probed.open_supported && pmu->probed.can_sample)
+ core_can_sample = true;
+ if (pmu->probed.open_supported && pmu->probed.max_precise > 0) {
+ core_precise = true;
+ if (pmu->probed.max_precise > max_precise)
+ max_precise = pmu->probed.max_precise;
+ }
+ if (pmu->probed.open_supported && pmu->probed.can_user_rdpmc)
+ core_rdpmc = true;
+ if (pmu->probed.open_supported && pmu->probed.can_branch_stack)
+ core_branch_stack = true;
+ if (pmu->probed.open_supported && pmu->probed.can_leader_sampling)
+ core_leader_sampling = true;
+ } else {
+ core_can_sample = true;
+ if (pmu->max_precise > 0) {
+ core_precise = true;
+ if (pmu->max_precise > max_precise)
+ max_precise = pmu->max_precise;
+ }
+ if (max_br_depth > 0)
+ core_branch_stack = true;
+ }
+ } else if (pmu->type == PERF_TYPE_SOFTWARE) {
+ if (!probed || !pmu->probed.checked || pmu->probed.can_sample)
+ has_sw_sampling = true;
+ }
+
+ if (pmu->mem_events) {
+ if (probed && pmu->probed.checked) {
+ if (pmu->probed.open_supported && pmu->probed.mem_events_mask)
+ core_mem_events = true;
+ } else if (!session) {
+ perf_pmu__mem_events_init_pmu(pmu);
+ for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) {
+ struct perf_mem_event *e =
+ perf_pmu__mem_events_ptr(pmu, j);
+
+ if (e && e->tag && e->supported) {
+ core_mem_events = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (pmu->auxtrace)
+ add_pmu_display_name(aux_pmus, pmu);
+ if (is_mem_bw_pmu_name(pmu->name))
+ add_pmu_display_name(mem_bw_pmus, pmu);
+ if (is_io_bw_pmu_name(pmu->name))
+ add_pmu_display_name(io_bw_pmus, pmu);
+ }
+
+ if (session) {
+ mtable = env->cpuid ?
+ find_core_metrics_table(perf_env__e_machine(env, /*e_flags=*/NULL),
+ env->cpuid) : NULL;
+ } else {
+ mtable = pmu_metrics_table__find();
+ }
+
+ metricgroup__for_each_metric(pmus, mtable, collect_summary_metric_cb, mstate);
+ max_drill_lvl = compute_metric_drilldown(mstate, level_counts, level_labels);
+
+ nr_core = strlist__nr_entries(core_pmus);
+ nr_metrics = strlist__nr_entries(mstate->metrics);
+ nr_groups = strlist__nr_entries(mstate->groups);
+
+ if (leading_newline)
+ fputc('\n', fp);
+ color_fprintf(fp, PERF_COLOR_BOLD, "Summary:");
+ fputc('\n', fp);
+ if (nr_core == 0) {
+ const char *sw_desc = has_sw_sampling ?
+ "minimal system; sampling only via software timer events" :
+ "no sampling support detected";
+
+ color_fprintf(fp, PERF_COLOR_BOLD, " Core PMUs:");
+ fprintf(fp, " ");
+ color_fprintf(fp, PERF_COLOR_RED, "none");
+ fprintf(fp, " (%s)\n", sw_desc);
+ strlist__add(missing, "hardware core PMU");
+ } else {
+ color_fprintf(fp, PERF_COLOR_BOLD, " Core PMUs (%zu%s):", nr_core,
+ nr_core > 1 ? ", hybrid" : "");
+ fprintf(fp, " ");
+ print_strlist_color(fp, PERF_COLOR_GREEN, core_pmus);
+ fputc('\n', fp);
+
+ color_fprintf(fp, PERF_COLOR_BOLD, " Core features:");
+ fputc('\n', fp);
+ fprintf(fp, " Interrupt-based sampling: ");
+ color_fprintf(fp, core_can_sample ? PERF_COLOR_GREEN : PERF_COLOR_RED,
+ "%s", core_can_sample ? "supported" : "missing");
+ fputc('\n', fp);
+ if (!core_can_sample)
+ strlist__add(missing, "core interrupt sampling");
+
+ fprintf(fp, " Precise event sampling: ");
+ if (core_precise) {
+ color_fprintf(fp, PERF_COLOR_GREEN, "supported");
+ fprintf(fp, " (max_precise: %d [%s])\n",
+ max_precise, precise_level_desc(max_precise));
+ } else {
+ color_fprintf(fp, PERF_COLOR_RED, "missing");
+ fprintf(fp, " (%s)\n", precise_level_desc(0));
+ strlist__add(missing, "precise event sampling (PEBS/IBS/SPE)");
+ }
+
+ fprintf(fp, " Branch stack sampling (LBR/BRBE): ");
+ if (core_branch_stack) {
+ color_fprintf(fp, PERF_COLOR_GREEN, "supported");
+ if (max_br_depth > 0)
+ fprintf(fp, " (depth: %d branches)\n", max_br_depth);
+ else
+ fputc('\n', fp);
+ } else {
+ color_fprintf(fp, PERF_COLOR_RED, "missing");
+ fputc('\n', fp);
+ strlist__add(missing, "hardware branch stack sampling (LBR/BRBE)");
+ }
+
+ if (core_probed) {
+ fprintf(fp, " User-space counter reading (rdpmc): ");
+ color_fprintf(fp, core_rdpmc ? PERF_COLOR_GREEN : PERF_COLOR_YELLOW,
+ "%s", core_rdpmc ? "supported" : "missing/disabled");
+ fputc('\n', fp);
+ if (!core_rdpmc)
+ strlist__add(missing, "user-space counter reading (rdpmc)");
+
+ fprintf(fp, " Leader sampling: ");
+ color_fprintf(fp,
+ core_leader_sampling ? PERF_COLOR_GREEN : PERF_COLOR_RED,
+ "%s",
+ core_leader_sampling ? "supported" : "missing");
+ fputc('\n', fp);
+ }
+
+ fprintf(fp, " Memory profiling events (DATA_SRC): ");
+ color_fprintf(fp, core_mem_events ? PERF_COLOR_GREEN : PERF_COLOR_RED,
+ "%s", core_mem_events ? "supported" : "missing");
+ fputc('\n', fp);
+ if (!core_mem_events)
+ strlist__add(missing, "memory load/store profiling events");
+
+ fprintf(fp, " Hardware AUX trace PMUs: ");
+ if (!strlist__empty(aux_pmus)) {
+ print_strlist_color(fp, PERF_COLOR_GREEN, aux_pmus);
+ fputc('\n', fp);
+ } else {
+ color_fprintf(fp, PERF_COLOR_RED, "missing");
+ fputc('\n', fp);
+ strlist__add(missing, "hardware AUX trace PMU");
+ }
+ }
+
+ color_fprintf(fp, PERF_COLOR_BOLD, " Bus & uncore monitoring:");
+ fputc('\n', fp);
+ fprintf(fp, " Memory bandwidth PMUs: ");
+ if (!strlist__empty(mem_bw_pmus)) {
+ print_strlist_color(fp, PERF_COLOR_GREEN, mem_bw_pmus);
+ fputc('\n', fp);
+ } else {
+ color_fprintf(fp, PERF_COLOR_RED, "missing");
+ fputc('\n', fp);
+ strlist__add(missing, "memory bus bandwidth PMUs");
+ }
+
+ fprintf(fp, " PCIe / IO interconnect bandwidth PMUs: ");
+ if (!strlist__empty(io_bw_pmus)) {
+ print_strlist_color(fp, PERF_COLOR_GREEN, io_bw_pmus);
+ fputc('\n', fp);
+ } else {
+ color_fprintf(fp, PERF_COLOR_RED, "missing");
+ fputc('\n', fp);
+ strlist__add(missing, "PCIe/IO interconnect bandwidth PMUs");
+ }
+
+ color_fprintf(fp, PERF_COLOR_BOLD, " Events & metrics:");
+ fputc('\n', fp);
+ fprintf(fp, " Available events: %zu total (%zu sysfs, %zu json) across %zu PMUs\n",
+ total_events, total_sysfs_events, total_json_events, total_pmus);
+ if (nr_metrics == 0) {
+ fprintf(fp, " Available metrics: ");
+ color_fprintf(fp, PERF_COLOR_RED, "none");
+ fprintf(fp, " (missing JSON metrics)\n");
+ strlist__add(missing, "multi-level pipeline drill-down and JSON metrics");
+ } else {
+ const char *tier = (max_drill_lvl >= 2) ? "advanced" : "minimal";
+ const char *tier_col = (max_drill_lvl >= 2) ?
+ PERF_COLOR_GREEN : PERF_COLOR_YELLOW;
+
+ fprintf(fp, " Available metrics: %zu metrics in %zu groups (",
+ nr_metrics, nr_groups);
+ color_fprintf(fp, tier_col, "%s", tier);
+ fprintf(fp, ")\n");
+ if (max_drill_lvl > 0) {
+ fprintf(fp, " Metric drill-down levels (%d): ", max_drill_lvl);
+ for (int d = 1; d <= max_drill_lvl; d++) {
+ if (d > 1)
+ fprintf(fp, ", ");
+ color_fprintf(fp, PERF_COLOR_CYAN, "L%d", d);
+ if (level_labels[d])
+ fprintf(fp, " (%s: %d metrics)",
+ level_labels[d], level_counts[d]);
+ else
+ fprintf(fp, " (%d metrics)", level_counts[d]);
+ }
+ fputc('\n', fp);
+ } else {
+ fprintf(fp, " Metric drill-down levels: ");
+ color_fprintf(fp, PERF_COLOR_YELLOW, "none");
+ fprintf(fp, " (flat metrics only)\n");
+ }
+ if (max_drill_lvl < 2) {
+ strlist__add(missing,
+ "multi-level pipeline drill-down metrics (<metric>_group)");
+ }
+ }
+
+ color_fprintf(fp, PERF_COLOR_BOLD, " System rating:");
+ if (nr_core == 0) {
+ const char *min_desc =
+ "no hardware core PMU; profiling limited to software timer events";
+
+ fprintf(fp, " ");
+ color_fprintf(fp, PERF_COLOR_RED, "Minimal");
+ fprintf(fp, " (%s)\n", min_desc);
+ } else if (core_precise && core_branch_stack && (!core_probed || core_rdpmc) &&
+ !strlist__empty(mem_bw_pmus) && max_drill_lvl >= 2) {
+ const char *core_adv = "precise sampling, branch stack (LBR), user rdpmc";
+ const char *sys_adv = "bus PMUs, and metric drill-down";
+
+ fprintf(fp, " ");
+ color_fprintf(fp, PERF_COLOR_GREEN, "Advanced");
+ fprintf(fp, " (core PMU with %s, %s)\n", core_adv, sys_adv);
+ } else if (core_can_sample &&
+ (core_precise || core_branch_stack || max_drill_lvl >= 1)) {
+ const char *std_desc =
+ "core PMU with hardware sampling and partial advanced features or metrics";
+
+ fprintf(fp, " ");
+ color_fprintf(fp, PERF_COLOR_CYAN, "Standard");
+ fprintf(fp, " (%s)\n", std_desc);
+ } else {
+ const char *bas_desc =
+ "core PMU with basic counting/sampling only; missing advanced features";
+
+ fprintf(fp, " ");
+ color_fprintf(fp, PERF_COLOR_YELLOW, "Basic");
+ fprintf(fp, " (%s)\n", bas_desc);
+ }
+
+ if (!strlist__empty(missing)) {
+ color_fprintf(fp, PERF_COLOR_BOLD, " Missing baseline features:");
+ fprintf(fp, " ");
+ print_strlist_color(fp, PERF_COLOR_RED, missing);
+ fputc('\n', fp);
+ }
+
+ for (int d = 1; d <= max_drill_lvl; d++)
+ free(level_labels[d]);
+
+out_free:
+ strlist__delete(core_pmus);
+ strlist__delete(mem_bw_pmus);
+ strlist__delete(io_bw_pmus);
+ strlist__delete(aux_pmus);
+ strlist__delete(missing);
+ if (mstate) {
+ for (size_t i = 0; i < mstate->nr_items; i++) {
+ free(mstate->items[i].pmu);
+ free(mstate->items[i].name);
+ free(mstate->items[i].groups);
+ }
+ strlist__delete(mstate->metrics);
+ strlist__delete(mstate->groups);
+ free(mstate);
+ }
+}
+
+int cmd_sysinfo(int argc, const char **argv)
+{
+ bool json = false;
+ bool show_pmus = false;
+ bool show_all = false;
+ bool force = false;
+ bool do_probe = true;
+ const char *output_path = NULL;
+ const char *input_name = NULL;
+ FILE *fp = stdout;
+ struct perf_data data = {
+ .mode = PERF_DATA_MODE_READ,
+ };
+ struct perf_tool tool;
+ struct perf_session *session = NULL;
+ struct perf_pmus *pmus;
+ struct perf_pmu *pmu = NULL;
+ size_t pmu_idx = 0;
+ int ret = 0;
+ const struct option sysinfo_options[] = {
+ OPT_STRING('i', "input", &input_name, "file",
+ "Input perf.data file name to read PMUs from"),
+ OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
+ OPT_STRING('o', "output", &output_path, "file", "output file name"),
+ OPT_BOOLEAN('p', "pmus", &show_pmus,
+ "Display detailed information for each PMU"),
+ OPT_BOOLEAN('j', "json", &json, "JSON encode PMU information"),
+ OPT_BOOLEAN('v', "verbose", &show_all,
+ "Show all similar PMUs with alphanumeric suffixes"),
+ OPT_BOOLEAN('a', "all", &show_all,
+ "Show all similar PMUs with alphanumeric suffixes"),
+ OPT_BOOLEAN(0, "probe", &do_probe,
+ "Probe PMU capabilities on live system (default: on)"),
+ OPT_INCR(0, "debug", &verbose, "Enable debugging output"),
+ OPT_END()
+ };
+ const char * const sysinfo_usage[] = {
+ "perf sysinfo [<options>] [pmu_glob ...]",
+ NULL
+ };
+
+ argc = parse_options(argc, argv, sysinfo_options, sysinfo_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (show_all || json || argc > 0)
+ show_pmus = true;
+
+ if (output_path) {
+ perf_use_color_default = 0;
+ fp = fopen(output_path, "w");
+ if (!fp) {
+ pr_err("Failed to open output file '%s': %m\n", output_path);
+ return -1;
+ }
+ }
+
+ if (input_name) {
+ data.path = input_name;
+ data.force = force;
+
+ perf_tool__init(&tool, /*ordered_events=*/false);
+ tool.attr = perf_event__process_attr;
+ /*
+ * In pipe mode event names follow the header features in
+ * event update records, so process events until the first
+ * sample.
+ */
+ tool.event_update = perf_event__process_event_update;
+ tool.feature = perf_event__process_feature;
+ tool.sample = process_sample_stop;
+
+ session = perf_session__new(&data, &tool);
+ if (IS_ERR(session)) {
+ ret = PTR_ERR(session);
+ pr_err("Error opening %s: %s\n", input_name, strerror(-ret));
+ goto out_close;
+ }
+
+ if (data.is_pipe)
+ perf_session__process_events(session);
+
+ ret = perf_pmus__read_from_env(&session->header.env.pmus,
+ &session->header.env);
+ if (ret) {
+ pr_err("Failed to read PMU information from %s: %s\n",
+ input_name, strerror(-ret));
+ goto out_session;
+ }
+ }
+
+ /*
+ * Use the PMUs read from the perf.data file even if it lacks PMU
+ * header features, in which case perf_env__pmus() would return the
+ * live host's PMUs.
+ */
+ pmus = session ? &session->header.env.pmus : perf_env__pmus(host_env());
+
+ if (json)
+ fputs("[", fp);
+
+ while ((pmu = show_all ? perf_pmus__scan(pmus, pmu) :
+ perf_pmus__scan_skip_duplicates(pmus, pmu)) != NULL) {
+ if (!pmu_matches_filters(pmu, show_all, argc, argv))
+ continue;
+
+ if (do_probe && !input_name &&
+ (show_pmus || pmu->is_core || pmu->mem_events ||
+ pmu->type == PERF_TYPE_SOFTWARE))
+ perf_pmu__probe(pmu);
+
+ if (!show_pmus)
+ continue;
+
+ if (json) {
+ if (pmu_idx > 0)
+ fputs(",\n", fp);
+ else
+ fputs("\n", fp);
+ print_pmu_json(fp, pmu, session, show_all);
+ } else {
+ if (pmu_idx > 0)
+ fputc('\n', fp);
+ print_pmu_text(fp, pmu, session, show_all);
+ }
+ pmu_idx++;
+ }
+
+ if (json)
+ fputs(pmu_idx > 0 ? "\n]\n" : "]\n", fp);
+ else if (argc == 0)
+ print_system_summary(fp, session, do_probe && !input_name,
+ pmu_idx > 0);
+
+out_session:
+ if (session)
+ perf_session__delete(session);
+out_close:
+ if (output_path && fp)
+ fclose(fp);
+ return ret;
+}
diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h
index e0b430821abc..28e85c054468 100644
--- a/tools/perf/builtin.h
+++ b/tools/perf/builtin.h
@@ -37,6 +37,7 @@ int cmd_list(int argc, const char **argv);
int cmd_record(int argc, const char **argv);
int cmd_report(int argc, const char **argv);
int cmd_stat(int argc, const char **argv);
+int cmd_sysinfo(int argc, const char **argv);
int cmd_timechart(int argc, const char **argv);
int cmd_top(int argc, const char **argv);
int cmd_script(int argc, const char **argv);
diff --git a/tools/perf/perf.c b/tools/perf/perf.c
index bcbc12295a38..f4ac149e9e8c 100644
--- a/tools/perf/perf.c
+++ b/tools/perf/perf.c
@@ -113,6 +113,7 @@ static const struct cmd_struct commands[] = {
{ "report", cmd_report, 0 },
{ "bench", cmd_bench, 0 },
{ "stat", cmd_stat, 0 },
+ { "sysinfo", cmd_sysinfo, 0 },
{ "timechart", cmd_timechart, 0 },
{ "top", cmd_top, 0 },
{ "annotate", cmd_annotate, 0 },
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 15/16] perf docs: Add perf-sysinfo man page
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (13 preceding siblings ...)
2026-11-13 19:55 ` [PATCH v2 14/16] perf sysinfo: Add sysinfo subcommand Ian Rogers
@ 2026-11-13 19:56 ` Ian Rogers
2026-11-13 19:56 ` [PATCH v2 16/16] perf test: Add perf sysinfo shell test Ian Rogers
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:56 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
Document the 'perf sysinfo' subcommand and its options in
Documentation/perf-sysinfo.txt and link it from Documentation/perf.txt.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/Documentation/perf-sysinfo.txt | 113 ++++++++++++++++++++++
tools/perf/Documentation/perf.txt | 2 +-
2 files changed, 114 insertions(+), 1 deletion(-)
create mode 100644 tools/perf/Documentation/perf-sysinfo.txt
diff --git a/tools/perf/Documentation/perf-sysinfo.txt b/tools/perf/Documentation/perf-sysinfo.txt
new file mode 100644
index 000000000000..c72ac6d51551
--- /dev/null
+++ b/tools/perf/Documentation/perf-sysinfo.txt
@@ -0,0 +1,113 @@
+perf-sysinfo(1)
+===============
+
+NAME
+----
+perf-sysinfo - Display and probe PMU and system performance monitoring information
+
+SYNOPSIS
+--------
+[verse]
+'perf sysinfo' [<options>] [pmu_glob ...]
+
+DESCRIPTION
+-----------
+This command displays a summary of the performance monitoring capabilities of
+the current system or a perf.data file, including core PMU features (precise
+sampling, branch stack sampling, user-space counter reading, memory profiling,
+and hardware AUX trace PMUs), memory and PCIe/IO interconnect bus bandwidth
+monitoring support, total events and metrics, pipeline metric drill-down levels
+discovered via '<metric>_group' hierarchies, and an overall system rating.
+
+When '-p'/'--pmus' is specified (or when PMU filter globs, '-v'/'--verbose',
+'-a'/'--all', or '-j'/'--json' are given), it also displays detailed
+information for each PMU, including its perf_event_attr type, kind (core,
+uncore, software, auxtrace, tool, hwmon, drm), CPUs/cpumask, capabilities,
+sysfs format bitfields, event counts, and memory profiling events.
+
+When run on a live system (without '-i'/'--input' and with probing enabled),
+it also actively probes the kernel via 'perf_event_open' to determine:
+- Supported modes (counting, interrupt-based sampling, branch_stack sampling)
+- Supported precise sampling levels, skid semantics, and empirically measured
+ branch instruction skid (minimum, maximum, average instructions from the
+ branch and 'PERF_RECORD_MISC_EXACT_IP' ratio) at each supported precision
+ level:
+ 0 ('SAMPLE_IP' can have arbitrary skid), ':p' / 1 ('SAMPLE_IP' must have
+ constant skid), ':pp' / 2 ('SAMPLE_IP' requested to have 0 skid), and
+ ':ppp' / 3 ('SAMPLE_IP' must have 0 skid, or uses randomization to avoid
+ sample shadowing effects)
+- Supported 'perf_event_sample_format' sample types ('ip', 'tid', 'time',
+ 'addr', 'read', 'callchain', 'id', 'cpu', 'period', 'stream_id', 'raw',
+ 'branch_stack', 'regs_user', 'stack_user', 'weight', 'data_src',
+ 'identifier', 'transaction', 'regs_intr', 'phys_addr', 'aux', 'cgroup',
+ 'data_page_size', 'code_page_size', 'weight_struct')
+- Supported user and interrupt registers for 'PERF_SAMPLE_REGS_USER' and
+ 'PERF_SAMPLE_REGS_INTR', listing separately the interrupt registers that
+ can only be sampled with precise sampling (e.g. x86 XMM registers captured
+ by PEBS)
+- Supported memory profiling events ('ldlat-loads', 'ldlat-stores', 'mem-ldst')
+ and their supported memory sample types ('data_src', 'addr', 'weight', etc.)
+- Supported privilege/exclusion modifiers ('exclude_user', 'exclude_kernel',
+ 'exclude_hv', 'exclude_idle', 'exclude_host', 'exclude_guest')
+- Supported execution features ('leader_sampling', 'sw_grouping', 'inherit',
+ 'sample_freq', 'write_backward', 'callchain', 'data_src', 'weight',
+ 'pinned', 'exclusive', 'format_lost', 'auto_counter_reload',
+ 'aux_start_paused', 'aux_pause', 'aux_resume', 'aux_sample', 'user_rdpmc',
+ 'user_time', 'user_time_zero', 'user_time_short') and hardware counter
+ bit-width ('pmc_width') when user-space counter reading is supported
+- Maximum number of events in a group counted simultaneously, without
+ multiplexing, and for core PMUs the maximum number of general-purpose
+ counter events. Groups larger than this may be accepted by
+ 'perf_event_open' but can't be counted together
+
+At the end of the text output, a summary section reports overall core PMU
+capabilities (including hardware AUX trace PMUs), memory and PCIe/IO
+interconnect bus bandwidth monitoring support, total events and metrics, and
+pipeline metric drill-down levels discovered via '<metric>_group' hierarchies.
+
+By default, numbered uncore PMU instances sharing a common prefix are collapsed
+into a single entry showing the instance count, unless '-v'/'--verbose' is
+specified.
+
+OPTIONS
+-------
+-i::
+--input=::
+ Input file name to read PMU information from (e.g. perf.data).
+
+-f::
+--force::
+ Don't complain, do it (when reading input perf.data file).
+
+-o::
+--output=::
+ Output file name.
+
+-p::
+--pmus::
+ Display detailed information for each PMU in addition to the system
+ summary.
+
+-j::
+--json::
+ Encode PMU information as JSON.
+
+-a::
+--all::
+-v::
+--verbose::
+ Display all PMUs including all similar PMUs with alphanumeric suffixes
+ instead of collapsing numbered uncore PMU instances.
+
+--probe::
+--no-probe::
+ Enable or disable active kernel probing of PMU capabilities on the live
+ system (default: enabled).
+
+--debug::
+ Enable debugging output.
+
+SEE ALSO
+--------
+linkperf:perf-list[1], linkperf:perf-evlist[1], linkperf:perf-stat[1],
+linkperf:perf-record[1]
diff --git a/tools/perf/Documentation/perf.txt b/tools/perf/Documentation/perf.txt
index cbcc2e4d557e..0890dd5a1794 100644
--- a/tools/perf/Documentation/perf.txt
+++ b/tools/perf/Documentation/perf.txt
@@ -92,5 +92,5 @@ linkperf:perf-help[1], linkperf:perf-inject[1],
linkperf:perf-intel-pt[1], linkperf:perf-iostat[1], linkperf:perf-kallsyms[1],
linkperf:perf-kmem[1], linkperf:perf-kvm[1], linkperf:perf-lock[1],
linkperf:perf-mem[1], linkperf:perf-probe[1], linkperf:perf-sched[1],
-linkperf:perf-script[1], linkperf:perf-test[1],
+linkperf:perf-script[1], linkperf:perf-sysinfo[1], linkperf:perf-test[1],
linkperf:perf-trace[1], linkperf:perf-version[1]
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 16/16] perf test: Add perf sysinfo shell test
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
` (14 preceding siblings ...)
2026-11-13 19:56 ` [PATCH v2 15/16] perf docs: Add perf-sysinfo man page Ian Rogers
@ 2026-11-13 19:56 ` Ian Rogers
15 siblings, 0 replies; 17+ messages in thread
From: Ian Rogers @ 2026-11-13 19:56 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
Cc: linux-perf-users, linux-kernel
Add a shell test exercising default 'perf sysinfo' summary output,
'--pmus' per-PMU output, '-j' JSON validation, PMU glob filtering, and
'-i perf.data' inspection.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/sysinfo.sh | 88 +++++++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100755 tools/perf/tests/shell/sysinfo.sh
diff --git a/tools/perf/tests/shell/sysinfo.sh b/tools/perf/tests/shell/sysinfo.sh
new file mode 100755
index 000000000000..743c8254b24f
--- /dev/null
+++ b/tools/perf/tests/shell/sysinfo.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf sysinfo tests
+
+set -e
+
+shelldir=$(dirname "$0")
+. "${shelldir}"/lib/setup_python.sh
+
+sysinfo_output=""
+perf_data=""
+
+cleanup() {
+ rm -f "${sysinfo_output}" "${perf_data}" ${perf_data:+"${perf_data}.old"}
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+sysinfo_output=$(mktemp /tmp/__perf_test.sysinfo_output.XXXXX)
+perf_data=$(mktemp /tmp/__perf_test.sysinfo.perf.data.XXXXX)
+
+test_sysinfo_default() {
+ echo "Default perf sysinfo output test"
+ perf sysinfo -o "${sysinfo_output}"
+ grep -q "^Summary:" "${sysinfo_output}"
+ grep -q "System rating:" "${sysinfo_output}"
+ perf sysinfo --pmus -o "${sysinfo_output}"
+ grep -q "^software (" "${sysinfo_output}"
+ grep -q "^Summary:" "${sysinfo_output}"
+ echo "Default perf sysinfo output test [Success]"
+}
+
+test_sysinfo_json() {
+ echo "Json perf sysinfo output test"
+ perf sysinfo -j -o "${sysinfo_output}"
+ $PYTHON -m json.tool "${sysinfo_output}" /dev/null
+ echo "Json perf sysinfo output test [Success]"
+}
+
+test_sysinfo_filter() {
+ echo "Filtered perf sysinfo output test"
+ perf sysinfo -o "${sysinfo_output}" software
+ grep -q "^software (" "${sysinfo_output}"
+ if grep -q "Modes:.*sampling" "${sysinfo_output}"; then
+ grep -q "Sample types:.*ip" "${sysinfo_output}"
+ # Register names are only known on some architectures.
+ case "$(uname -m)" in
+ x86_64|aarch64)
+ if grep -q "Sample types:.*regs_user" "${sysinfo_output}"; then
+ grep -q "User registers:" "${sysinfo_output}"
+ fi
+ if grep -q "Sample types:.*regs_intr" "${sysinfo_output}"; then
+ grep -q "Intr registers:" "${sysinfo_output}"
+ fi
+ ;;
+ esac
+ grep -q "Features:.*leader_sampling" "${sysinfo_output}"
+ grep -q "Features:.*sw_grouping" "${sysinfo_output}"
+ fi
+ echo "Filtered perf sysinfo output test [Success]"
+}
+
+test_sysinfo_perf_data() {
+ echo "perf.data perf sysinfo test"
+ if ! perf record -e software/cpu-clock/u -o "${perf_data}" -- true 2>/dev/null; then
+ echo "perf.data perf sysinfo test [Skipped: perf record failed]"
+ return
+ fi
+ perf sysinfo -i "${perf_data}" -o "${sysinfo_output}"
+ grep -q "^Summary:" "${sysinfo_output}"
+ perf sysinfo --pmus -i "${perf_data}" -o "${sysinfo_output}"
+ grep -q "software" "${sysinfo_output}"
+ perf sysinfo -i "${perf_data}" -j -o "${sysinfo_output}"
+ $PYTHON -m json.tool "${sysinfo_output}" /dev/null
+ echo "perf.data perf sysinfo test [Success]"
+}
+
+test_sysinfo_default
+test_sysinfo_json
+test_sysinfo_filter
+test_sysinfo_perf_data
+cleanup
+exit 0
--
2.56.1.703.gf848ed807c-goog
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-11-13 19:56 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-13 19:55 [PATCH v2 00/16] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers
2026-11-13 19:55 ` [PATCH v2 01/16] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers
2026-11-13 19:55 ` [PATCH v2 02/16] perf mem-events: Expose PMU init and name formatting helpers Ian Rogers
2026-11-13 19:55 ` [PATCH v2 03/16] perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling Ian Rogers
2026-11-13 19:55 ` [PATCH v2 04/16] perf pmu: Probe supported sample_type flags Ian Rogers
2026-11-13 19:55 ` [PATCH v2 05/16] perf pmu: Probe precise_ip levels Ian Rogers
2026-11-13 19:55 ` [PATCH v2 06/16] perf pmu: Probe privilege exclusion modifiers Ian Rogers
2026-11-13 19:55 ` [PATCH v2 07/16] perf pmu: Probe execution and AUX area features Ian Rogers
2026-11-13 19:55 ` [PATCH v2 08/16] perf pmu: Probe event group size limits Ian Rogers
2026-11-13 19:55 ` [PATCH v2 09/16] perf pmu: Probe user and interrupt sample register masks Ian Rogers
2026-11-13 19:55 ` [PATCH v2 10/16] perf pmu: Probe interrupt registers that require precise sampling Ian Rogers
2026-11-13 19:55 ` [PATCH v2 11/16] perf pmu: Probe memory profiling events and sample formats Ian Rogers
2026-11-13 19:55 ` [PATCH v2 12/16] perf pmu: Probe user-space mmap capabilities Ian Rogers
2026-11-13 19:55 ` [PATCH v2 13/16] perf pmu: Probe branch instruction skid Ian Rogers
2026-11-13 19:55 ` [PATCH v2 14/16] perf sysinfo: Add sysinfo subcommand Ian Rogers
2026-11-13 19:56 ` [PATCH v2 15/16] perf docs: Add perf-sysinfo man page Ian Rogers
2026-11-13 19:56 ` [PATCH v2 16/16] perf test: Add perf sysinfo shell test Ian Rogers
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®