Linux Perf Users
 help / color / mirror / Atom feed
* [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU
@ 2026-10-16  0:18 Arnaldo Carvalho de Melo
  2026-10-16  0:18 ` [PATCH v3 01/19] perf test attr: Fix record dwarf sample_type expectation Arnaldo Carvalho de Melo
                   ` (18 more replies)
  0 siblings, 19 replies; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo, Michael Petlan

From: Arnaldo Carvalho de Melo <acme@redhat.com>

Hello all,

perf test 82 stopped reporting failures when it got its shell wrapper
(8519e4f44c2af722), so expectations that no longer match perf went
unverified.  The prerequisite fixes are upstream now, so they are not
repeated here.

This series updates those expectations and makes the test cope with
machines with no core PMU, or with more than one.  Asking for one event
on the latter gives one event per core PMU.  The expectations ignore
what that adds; the commit messages say what and why.

The tests that map ring buffers take turns on the per user mlock budget
(kernel.perf_event_mlock_kb) through a robust, process-shared mutex.
The sysctl is left alone.  A test killed while holding the budget does
not leave the next one waiting.

Tested: 'perf test -vv attribute' Ok on an Amlogic A311D (aarch64, as
root), an Intel i7-14700K (hybrid, two core PMUs), x86_64 (AMD Ryzen 9
9950X3D, kernel 7.2.7) and riscv64 (Orange Pi RV).  On riscv64 the stat
tests run and the record tests report unsupp, as sampling is not
available there (no Sscofpmf).

Best regards,

- Arnaldo

What changed from v2:

  - sashiko-bot's v2 review: a one-component kernel_since or
    kernel_until (f.i. kernel_until = 6) no longer crashes attr.py.
  - sashiko-bot's v2 review: mutex_trylock() and cond_wait() now take
    over a robust mutex left behind by a dead owner, as mutex_lock().

What changed from v1:

  - Namhyung's python fix and Michael Petlan's two fixes are upstream
    now, so they are dropped from the series.
  - Namhyung's v1 report (test-record-branch-filter-any_call): probe
    events are no longer dumped, and the core PMU fallout is masked.
  - New, not from review: the 'requires = core-pmu' skip, the skip
    reasons and the mlock budget part.
  - New, not from review: the events of the other core PMUs are left
    out, when the expectations name a core PMU.

 tools/build/Makefile.feature                       |   1 +
 tools/build/feature/Makefile                       |   4 +
 tools/build/feature/test-all.c                     |   5 +
 tools/build/feature/test-pthread-mutex-robust.c    |  18 +
 tools/perf/Documentation/perf-test.txt             |  36 +-
 tools/perf/Makefile.config                         |   4 +
 tools/perf/tests/backward-ring-buffer.c            |   2 +-
 tools/perf/tests/builtin-test.c                    | 173 +++++++-
 tools/perf/tests/code-reading.c                    |   2 +-
 tools/perf/tests/keep-tracking.c                   |   2 +-
 tools/perf/tests/mmap-basic.c                      |   1 +
 tools/perf/tests/openat-syscall-tp-fields.c        |   1 +
 tools/perf/tests/perf-record.c                     |   1 +
 tools/perf/tests/perf-time-to-tsc.c                |   1 +
 tools/perf/tests/shell/attr/README                 |  10 +-
 tools/perf/tests/shell/attr/base-stat-group        |  45 ++
 .../perf/tests/shell/attr/test-record-graph-dwarf  |   2 +-
 .../tests/shell/attr/test-record-group-sampling    |   5 +-
 .../tests/shell/attr/test-record-group-sampling1   |   5 +-
 .../tests/shell/attr/test-record-group-sampling2   |   5 +-
 tools/perf/tests/shell/attr/test-record-group1     |   3 +-
 tools/perf/tests/shell/attr/test-record-group2     |   3 +-
 tools/perf/tests/shell/attr/test-record-group3     |   3 +-
 tools/perf/tests/shell/attr/test-stat-C0           |   1 +
 tools/perf/tests/shell/attr/test-stat-basic        |   1 +
 tools/perf/tests/shell/attr/test-stat-default      |  91 +++--
 tools/perf/tests/shell/attr/test-stat-detailed-1   |  99 +++--
 tools/perf/tests/shell/attr/test-stat-detailed-2   | 111 +++--
 tools/perf/tests/shell/attr/test-stat-detailed-3   | 115 ++++--
 tools/perf/tests/shell/attr/test-stat-group1       |   3 +-
 tools/perf/tests/shell/attr/test-stat-no-inherit   |   1 +
 tools/perf/tests/shell/drm_pmu.sh                  |   2 +-
 tools/perf/tests/shell/ftrace.sh                   |   2 +-
 tools/perf/tests/shell/kallsyms.sh                 |   2 +-
 tools/perf/tests/shell/lib/attr.py                 | 455 +++++++++++++++++++--
 tools/perf/tests/shell/list.sh                     |   2 +-
 tools/perf/tests/shell/perf_sched_stats.sh         |   2 +-
 tools/perf/tests/shell/probe_vfs_getname.sh        |   2 +-
 tools/perf/tests/shell/python-use.sh               |   2 +-
 tools/perf/tests/shell/schedstat_snapshots.sh      |   2 +-
 tools/perf/tests/shell/stat+csv_output.sh          |   2 +-
 tools/perf/tests/shell/stat+csv_summary.sh         |   2 +-
 tools/perf/tests/shell/stat+event_uniquifying.sh   |   2 +-
 tools/perf/tests/shell/stat+json_output.sh         |   2 +-
 tools/perf/tests/shell/stat+shadow_stat.sh         |   2 +-
 tools/perf/tests/shell/stat+std_output.sh          |   2 +-
 tools/perf/tests/shell/stat.sh                     |   2 +-
 tools/perf/tests/shell/stat_all_metricgroups.sh    |   2 +-
 tools/perf/tests/shell/stat_all_metrics.sh         |   2 +-
 tools/perf/tests/shell/stat_all_pfm.sh             |   2 +-
 tools/perf/tests/shell/stat_bpf_counters_cgrp.sh   |   2 +-
 tools/perf/tests/shell/stat_metrics_cgrp.sh        |   2 +-
 tools/perf/tests/shell/stat_metrics_values.sh      |   2 +-
 tools/perf/tests/shell/test_ilist_python.sh        |   2 +-
 tools/perf/tests/shell/test_stat_cpi_python.sh     |   2 +-
 tools/perf/tests/shell/test_test_junit_output.sh   |   5 +-
 .../tests/shell/test_uprobe_from_different_cu.sh   |   2 +-
 tools/perf/tests/sw-clock.c                        |   2 +-
 tools/perf/tests/task-exit.c                       |   1 +
 tools/perf/tests/tests-scripts.c                   |  84 +++-
 tools/perf/tests/tests.h                           |  17 +
 tools/perf/util/evsel.c                            |  12 +-
 tools/perf/util/evsel.h                            |   1 +
 tools/perf/util/mutex.c                            | 101 ++++-
 tools/perf/util/mutex.h                            |   6 +
 tools/perf/util/pfm.c                              |   1 +
 tools/perf/util/print-events.c                     |   1 +
 67 files changed, 1225 insertions(+), 263 deletions(-)

base-commit: cb9dba6f4764fc68ab75c36e332887332339f9c3
v1-head: b1300831ecc4503d
v2-head: da166ef7a002

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

* [PATCH v3 01/19] perf test attr: Fix record dwarf sample_type expectation
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:22   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 02/19] perf test attr: Allow tests to require a core PMU Arnaldo Carvalho de Melo
                   ` (17 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

Since commit bdd051e24914 ("perf record: Split --data-mmap option"),
record_opts__parse_callchain() sets record_data_mmap instead of
record->sample_address for the DWARF unwind mode, so -d/--data and its
implied PERF_SAMPLE_DATA_SRC are not enabled implicitly anymore.

As a result, --call-graph dwarf no longer sets PERF_SAMPLE_ADDR and
PERF_SAMPLE_DATA_SRC, making the test fail:

  expected sample_type=45359, got 12583

where 45359 (0xb12f) is:

  IP|TID|TIME|ADDR|CALLCHAIN|PERIOD|REGS_USER|STACK_USER|DATA_SRC

and 12583 (0x3127) is:

  IP|TID|TIME|CALLCHAIN|PERIOD|REGS_USER|STACK_USER

i.e. the values differ exactly by PERF_SAMPLE_ADDR (0x8) and
PERF_SAMPLE_DATA_SRC (0x8000).  The mmap_data=1 expectation is still
correct as DWARF now enables record_data_mmap.

This went unnoticed until tests/attr.sh stopped ignoring attr.py's
return value.

Fixes: bdd051e249141c79 ("perf record: Split --data-mmap option")
Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/attr/test-record-graph-dwarf | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/perf/tests/shell/attr/test-record-graph-dwarf b/tools/perf/tests/shell/attr/test-record-graph-dwarf
index ae92061d611ded68..fbe0faa8cdb6a8f4 100644
--- a/tools/perf/tests/shell/attr/test-record-graph-dwarf
+++ b/tools/perf/tests/shell/attr/test-record-graph-dwarf
@@ -4,7 +4,7 @@ args    = --no-bpf-event --call-graph dwarf -- kill >/dev/null 2>&1
 ret     = 1
 
 [event:base-record]
-sample_type=45359
+sample_type=12583
 exclude_callchain_user=1
 sample_stack_user=8192
 # TODO different for each arch, no support for that now
-- 
2.55.0


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

* [PATCH v3 02/19] perf test attr: Allow tests to require a core PMU
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
  2026-10-16  0:18 ` [PATCH v3 01/19] perf test attr: Fix record dwarf sample_type expectation Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:26   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 03/19] perf test attr: Fix legacy event encodings in group tests Arnaldo Carvalho de Melo
                   ` (16 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

On machines without a usable core PMU, like an aarch64 box with no DT
node for the ARM PMU, the expectations that pin the legacy hardware
encoding can't be checked: without a core PMU at all 'cycles' doesn't
even resolve, and with one that can't be opened 'perf record' takes the
software fallback and records attributes they can never match:

  expected type=0, got 1
  expected config=0, got 1
  FAILED '.../attr/test-record-group-sampling1' - match failure

And since attr.py stops at the first failure, every expectation that
would come after that one ends up not being run at all.

Add a 'requires' limitation, modelled after the existing 'arch', 'auxv'
and 'kernel_since/until' ones, with 'core-pmu' as its first value, that
is checked by asking perf itself to open 'cycles' via its python
binding: a parse failure or an ENOENT means there is no PMU that can
count it, hence nothing to check and the test gets skipped, while any
other error means we can't tell, so the test keeps running as it does
today, where there is no binding to ask either.

When there is no core PMU, asking the binding fails and prints a parse
error, once for every test that asks, so the answer is kept: it is asked
once per run.  A value other than 'core-pmu' is warned about, not
silently ignored, so a typo can't turn the skip into a no-op.

An old distro python package may have a 'perf' module without
parse_events(), i.e. there is nothing to ask with, so that
AttributeError gets the same treatment as the ImportError: let the
expectations speak.

The expectations that have a strict 'type=0', plus the group tests,
where the fallback drops a member and leaves nothing to compare the
group leader attributes with, now state that they require a core PMU.
They are marked here, before the commit that has the group tests ask for
the legacy encodings, so the requisite comes with that dependency.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 .../shell/attr/test-record-group-sampling     |  1 +
 .../shell/attr/test-record-group-sampling1    |  1 +
 .../shell/attr/test-record-group-sampling2    |  1 +
 .../perf/tests/shell/attr/test-record-group1  |  1 +
 .../perf/tests/shell/attr/test-record-group2  |  1 +
 .../perf/tests/shell/attr/test-record-group3  |  1 +
 tools/perf/tests/shell/attr/test-stat-C0      |  1 +
 tools/perf/tests/shell/attr/test-stat-basic   |  1 +
 tools/perf/tests/shell/attr/test-stat-default |  1 +
 .../tests/shell/attr/test-stat-detailed-1     |  1 +
 .../tests/shell/attr/test-stat-detailed-2     |  1 +
 .../tests/shell/attr/test-stat-detailed-3     |  1 +
 tools/perf/tests/shell/attr/test-stat-group1  |  1 +
 .../tests/shell/attr/test-stat-no-inherit     |  1 +
 tools/perf/tests/shell/lib/attr.py            | 63 +++++++++++++++++++
 15 files changed, 77 insertions(+)

diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling b/tools/perf/tests/shell/attr/test-record-group-sampling
index 86a940d7895d7404..b43d9fe3af75c096 100644
--- a/tools/perf/tests/shell/attr/test-record-group-sampling
+++ b/tools/perf/tests/shell/attr/test-record-group-sampling
@@ -3,6 +3,7 @@ command = record
 args    = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
 ret     = 1
 kernel_until = 6.12
+requires = core-pmu
 
 [event-1:base-record]
 fd=1
diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling1 b/tools/perf/tests/shell/attr/test-record-group-sampling1
index 4748ab7bf68437c7..3def72161820dca3 100644
--- a/tools/perf/tests/shell/attr/test-record-group-sampling1
+++ b/tools/perf/tests/shell/attr/test-record-group-sampling1
@@ -3,6 +3,7 @@ command = record
 args    = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
 ret     = 1
 kernel_since = 6.12
+requires = core-pmu
 
 [event-1:base-record]
 fd=1
diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling2 b/tools/perf/tests/shell/attr/test-record-group-sampling2
index e0432244a0eb7f6c..227c18bb221c4b2f 100644
--- a/tools/perf/tests/shell/attr/test-record-group-sampling2
+++ b/tools/perf/tests/shell/attr/test-record-group-sampling2
@@ -3,6 +3,7 @@ command = record
 args    = --no-bpf-event -c 10000 -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
 ret     = 1
 kernel_since = 6.12
+requires = core-pmu
 
 [event-1:base-record]
 fd=1
diff --git a/tools/perf/tests/shell/attr/test-record-group1 b/tools/perf/tests/shell/attr/test-record-group1
index eeb1db392bc9ceac..a57c7ee8aaa4f9c9 100644
--- a/tools/perf/tests/shell/attr/test-record-group1
+++ b/tools/perf/tests/shell/attr/test-record-group1
@@ -2,6 +2,7 @@
 command = record
 args    = --no-bpf-event -e '{cycles,instructions}' kill >/dev/null 2>&1
 ret     = 1
+requires = core-pmu
 
 [event-1:base-record]
 fd=1
diff --git a/tools/perf/tests/shell/attr/test-record-group2 b/tools/perf/tests/shell/attr/test-record-group2
index 891d41a7bddf785a..fb6e664eeacc10ef 100644
--- a/tools/perf/tests/shell/attr/test-record-group2
+++ b/tools/perf/tests/shell/attr/test-record-group2
@@ -3,6 +3,7 @@ command = record
 args    = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1
 ret     = 1
 kernel_until = 6.12
+requires = core-pmu
 
 [event-1:base-record]
 fd=1
diff --git a/tools/perf/tests/shell/attr/test-record-group3 b/tools/perf/tests/shell/attr/test-record-group3
index 249be884959eaaba..7b90b4411d8f4b90 100644
--- a/tools/perf/tests/shell/attr/test-record-group3
+++ b/tools/perf/tests/shell/attr/test-record-group3
@@ -3,6 +3,7 @@ command = record
 args    = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1
 ret     = 1
 kernel_since = 6.12
+requires = core-pmu
 
 [event-1:base-record]
 fd=1
diff --git a/tools/perf/tests/shell/attr/test-stat-C0 b/tools/perf/tests/shell/attr/test-stat-C0
index a2c76d10b2bb2a15..9d242e93e3a92009 100644
--- a/tools/perf/tests/shell/attr/test-stat-C0
+++ b/tools/perf/tests/shell/attr/test-stat-C0
@@ -2,6 +2,7 @@
 command = stat
 args    = -e cycles -C 0 kill >/dev/null 2>&1
 ret     = 1
+requires = core-pmu
 
 [event:base-stat]
 # events are disabled by default when attached to cpu
diff --git a/tools/perf/tests/shell/attr/test-stat-basic b/tools/perf/tests/shell/attr/test-stat-basic
index 69867d049fda926f..277897bd2626a41f 100644
--- a/tools/perf/tests/shell/attr/test-stat-basic
+++ b/tools/perf/tests/shell/attr/test-stat-basic
@@ -2,6 +2,7 @@
 command = stat
 args    = -e cycles kill >/dev/null 2>&1
 ret     = 1
+requires = core-pmu
 
 [event:base-stat]
 optional=1
diff --git a/tools/perf/tests/shell/attr/test-stat-default b/tools/perf/tests/shell/attr/test-stat-default
index 8dd27c1fb661fb77..74691188e859a169 100644
--- a/tools/perf/tests/shell/attr/test-stat-default
+++ b/tools/perf/tests/shell/attr/test-stat-default
@@ -2,6 +2,7 @@
 command = stat
 args    = kill >/dev/null 2>&1
 ret     = 1
+requires = core-pmu
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
 [event1:base-stat]
diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-1 b/tools/perf/tests/shell/attr/test-stat-detailed-1
index 12a2ebf4e64a0077..d9a6236054f2dfb1 100644
--- a/tools/perf/tests/shell/attr/test-stat-detailed-1
+++ b/tools/perf/tests/shell/attr/test-stat-detailed-1
@@ -2,6 +2,7 @@
 command = stat
 args    = -d kill >/dev/null 2>&1
 ret     = 1
+requires = core-pmu
 
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-2 b/tools/perf/tests/shell/attr/test-stat-detailed-2
index 66ea25b7d38f8d59..582a30289ccfa6df 100644
--- a/tools/perf/tests/shell/attr/test-stat-detailed-2
+++ b/tools/perf/tests/shell/attr/test-stat-detailed-2
@@ -2,6 +2,7 @@
 command = stat
 args    = -dd kill >/dev/null 2>&1
 ret     = 1
+requires = core-pmu
 
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-3 b/tools/perf/tests/shell/attr/test-stat-detailed-3
index 4a27bbfb9f87a538..2d0fca8b5020f504 100644
--- a/tools/perf/tests/shell/attr/test-stat-detailed-3
+++ b/tools/perf/tests/shell/attr/test-stat-detailed-3
@@ -2,6 +2,7 @@
 command = stat
 args    = -ddd kill >/dev/null 2>&1
 ret     = 1
+requires = core-pmu
 
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
diff --git a/tools/perf/tests/shell/attr/test-stat-group1 b/tools/perf/tests/shell/attr/test-stat-group1
index 1746751123dcb2c4..3d8b44ba42d6ae19 100644
--- a/tools/perf/tests/shell/attr/test-stat-group1
+++ b/tools/perf/tests/shell/attr/test-stat-group1
@@ -2,6 +2,7 @@
 command = stat
 args    = -e '{cycles,instructions}' kill >/dev/null 2>&1
 ret     = 1
+requires = core-pmu
 
 [event-1:base-stat]
 fd=1
diff --git a/tools/perf/tests/shell/attr/test-stat-no-inherit b/tools/perf/tests/shell/attr/test-stat-no-inherit
index 924fbb9300d16259..38f746855b1b3e11 100644
--- a/tools/perf/tests/shell/attr/test-stat-no-inherit
+++ b/tools/perf/tests/shell/attr/test-stat-no-inherit
@@ -2,6 +2,7 @@
 command = stat
 args    = -i -e cycles kill >/dev/null 2>&1
 ret     = 1
+requires = core-pmu
 
 [event:base-stat]
 inherit=0
diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
index 68f06df9565a0811..2d7dd26a5737777c 100644
--- a/tools/perf/tests/shell/lib/attr.py
+++ b/tools/perf/tests/shell/lib/attr.py
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 import configparser
+import errno
 import os
 import sys
 import glob
@@ -28,6 +29,50 @@ def data_equal(a, b):
 
     return False
 
+CORE_PMU_AVAILABLE = None
+
+def core_pmu_available():
+    """
+    Is there a core PMU on this machine? Without one hardware events aren't
+    supported and get replaced by the software fallback, so there's no way to
+    check the expectations that pin their legacy encodings.  The answer can't
+    change while the tests run, so probe the binding only once.
+    """
+    global CORE_PMU_AVAILABLE
+
+    if CORE_PMU_AVAILABLE is None:
+        CORE_PMU_AVAILABLE = core_pmu_probe()
+
+    return CORE_PMU_AVAILABLE
+
+def core_pmu_probe():
+    """
+    Ask perf itself, through its python binding, to parse and open 'cycles':
+    a parse failure or an ENOENT means no PMU can count it, any other error
+    means we can't tell whether there is one.
+    """
+    try:
+        import perf
+        parse_events = perf.parse_events
+    except (ImportError, AttributeError):
+        # Nothing to ask about it, let the expectations speak.
+        return True
+
+    try:
+        evlist = parse_events('cycles')
+    except OSError:
+        # No PMU that can count it, the same as the ENOENT below.
+        return False
+
+    try:
+        evlist.open()
+    except OSError as err:
+        # Anything else is a permission problem or alike, not our business here.
+        return err.errno != errno.ENOENT
+    evlist.close()
+
+    return True
+
 class Fail(Exception):
     def __init__(self, test, msg):
         self.msg = msg
@@ -138,6 +183,9 @@ def parse_version(version):
 #     'arch'    - architecture specific test (optional)
 #                 comma separated list, ! at the beginning
 #                 negates it.
+#     'requires' - Comma separated list of things the test needs, it is
+#                 skipped when any of them isn't there, currently:
+#                   core-pmu - the machine can count hardware events.
 #     'auxv'    - Truthy statement that is evaled in the scope of the auxv map. When false,
 #                 the test is skipped. For example 'auxv["AT_HWCAP"] == 10'. (optional)
 #     'kernel_since' - Inclusive kernel version from which the test will start running. Only the
@@ -173,6 +221,7 @@ class Test(object):
             self.arch  = ''
 
         self.auxv = parser.get('config', 'auxv', fallback=None)
+        self.requires = parser.get('config', 'requires', fallback='')
         self.kernel_since = parse_version(parser.get('config', 'kernel_since', fallback=None))
         self.kernel_until = parse_version(parser.get('config', 'kernel_until', fallback=None))
         self.expect   = {}
@@ -242,6 +291,17 @@ class Test(object):
                 return False
         return True
 
+    def skip_test_requires(self):
+        for requirement in filter(None, map(str.strip, self.requires.split(','))):
+            if requirement == 'core-pmu':
+                if not core_pmu_available():
+                    log.warning("test limitation 'requires %s'", requirement)
+                    return True
+            else:
+                log.warning("unknown requirement '%s', ignored", requirement)
+
+        return False
+
     def restore_sample_rate(self, value=10000):
         try:
             # Check value of sample_rate
@@ -296,6 +356,9 @@ class Test(object):
         if self.skip_test_kernel_until():
             raise Notest(self, "new kernel skip")
 
+        if self.skip_test_requires():
+            raise Notest(self, "requirements skip")
+
         self.restore_sample_rate()
         cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir,
               self.perf, self.command, tempdir, self.args)
-- 
2.55.0


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

* [PATCH v3 03/19] perf test attr: Fix legacy event encodings in group tests
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
  2026-10-16  0:18 ` [PATCH v3 01/19] perf test attr: Fix record dwarf sample_type expectation Arnaldo Carvalho de Melo
  2026-10-16  0:18 ` [PATCH v3 02/19] perf test attr: Allow tests to require a core PMU Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:32   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 04/19] perf test attr: Fix default stat metrics expectations Arnaldo Carvalho de Melo
                   ` (15 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

Since commit b1c5efbfd92e ("perf parse-events: Remove hard coded legacy
hardware and cache parsing"), event names are resolved with sysfs/JSON
priority over the legacy encodings, so bare cache-misses and
instructions now become the core PMU sysfs/JSON events (on x86 type 4,
configs 0x412e and 0xc0) rather than the legacy
PERF_TYPE_HARDWARE/config=3 and config=1 events the tests expect, e.g.:

  expected config=3, got 0
  expected disabled=0, got 1
  expected mmap=0, got 1
  expected comm=0, got 1
  expected freq=0, got 1
  expected enable_on_exec=0, got 1
  expected task=0, got 1
  FAILED '.../test-record-group-sampling1' - match failure

The shown diff is against the cycles group leader, as no result event
matches the expected cache-misses member anymore.

Request the legacy encodings explicitly with the legacy-hardware-config
term, added in commit 0012e0fa221bf9cc ("perf jevents: Add
legacy-hardware and legacy-cache json"), keeping the original
attribute expectations unchanged, in:

  test-record-group1
  test-record-group2
  test-record-group3
  test-record-group-sampling
  test-record-group-sampling1
  test-record-group-sampling2
  test-stat-group1

This went unnoticed until tests/attr.sh stopped ignoring attr.py's
return value.

Fixes: b1c5efbfd92eb84d ("perf parse-events: Remove hard coded legacy hardware and cache parsing")
Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/attr/README                     | 10 +++++-----
 tools/perf/tests/shell/attr/test-record-group-sampling |  4 ++--
 .../perf/tests/shell/attr/test-record-group-sampling1  |  4 ++--
 .../perf/tests/shell/attr/test-record-group-sampling2  |  4 ++--
 tools/perf/tests/shell/attr/test-record-group1         |  2 +-
 tools/perf/tests/shell/attr/test-record-group2         |  2 +-
 tools/perf/tests/shell/attr/test-record-group3         |  2 +-
 tools/perf/tests/shell/attr/test-stat-group1           |  2 +-
 8 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/tools/perf/tests/shell/attr/README b/tools/perf/tests/shell/attr/README
index 67c4ca76b85d5611..c7b49cdf87297177 100644
--- a/tools/perf/tests/shell/attr/README
+++ b/tools/perf/tests/shell/attr/README
@@ -49,10 +49,10 @@ Following tests are defined (with perf commands):
   perf record --call-graph dwarf kill		(test-record-graph-dwarf)
   perf record --call-graph fp kill              (test-record-graph-fp)
   perf record --call-graph fp kill              (test-record-graph-fp-aarch64)
-  perf record -e '{cycles,instructions}' kill   (test-record-group1)
-  perf record -e '{cycles/period=1/,instructions/period=2/}:S' kill (test-record-group2)
-  perf record -e '{cycles,cache-misses}:S' kill (test-record-group-sampling1)
-  perf record -c 10000 -e '{cycles,cache-misses}:S' kill (test-record-group-sampling2)
+  perf record -e '{cycles,default_core/legacy-hardware-config=1/}' kill (test-record-group1)
+  perf record -e '{cycles/period=1234000/,default_core/legacy-hardware-config=1,period=6789000/}:S' kill (test-record-group2)
+  perf record -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill (test-record-group-sampling1)
+  perf record -c 10000 -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill (test-record-group-sampling2)
   perf record -D kill                           (test-record-no-delay)
   perf record -i kill                           (test-record-no-inherit)
   perf record -n kill                           (test-record-no-samples)
@@ -67,5 +67,5 @@ Following tests are defined (with perf commands):
   perf stat -d kill                             (test-stat-detailed-1)
   perf stat -dd kill                            (test-stat-detailed-2)
   perf stat -ddd kill                           (test-stat-detailed-3)
-  perf stat -e '{cycles,instructions}' kill     (test-stat-group1)
+  perf stat -e '{cycles,default_core/legacy-hardware-config=1/}' kill (test-stat-group1)
   perf stat -i -e cycles kill                   (test-stat-no-inherit)
diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling b/tools/perf/tests/shell/attr/test-record-group-sampling
index b43d9fe3af75c096..ed47dd1e5984535b 100644
--- a/tools/perf/tests/shell/attr/test-record-group-sampling
+++ b/tools/perf/tests/shell/attr/test-record-group-sampling
@@ -1,6 +1,6 @@
 [config]
 command = record
-args    = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
+args    = --no-bpf-event -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill >/dev/null 2>&1
 ret     = 1
 kernel_until = 6.12
 requires = core-pmu
@@ -16,7 +16,7 @@ inherit=0
 fd=2
 group_fd=1
 
-# cache-misses
+# cache-misses, explicitly requested with the legacy hardware encoding
 type=0
 config=3
 
diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling1 b/tools/perf/tests/shell/attr/test-record-group-sampling1
index 3def72161820dca3..9cf013dc03dd0309 100644
--- a/tools/perf/tests/shell/attr/test-record-group-sampling1
+++ b/tools/perf/tests/shell/attr/test-record-group-sampling1
@@ -1,6 +1,6 @@
 [config]
 command = record
-args    = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
+args    = --no-bpf-event -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill >/dev/null 2>&1
 ret     = 1
 kernel_since = 6.12
 requires = core-pmu
@@ -31,7 +31,7 @@ inherit=1
 fd=2
 group_fd=1
 
-# cache-misses
+# cache-misses, explicitly requested with the legacy hardware encoding
 type=0
 config=3
 
diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling2 b/tools/perf/tests/shell/attr/test-record-group-sampling2
index 227c18bb221c4b2f..bd1eb4161e4aee46 100644
--- a/tools/perf/tests/shell/attr/test-record-group-sampling2
+++ b/tools/perf/tests/shell/attr/test-record-group-sampling2
@@ -1,6 +1,6 @@
 [config]
 command = record
-args    = --no-bpf-event -c 10000 -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
+args    = --no-bpf-event -c 10000 -e '{cycles,default_core/legacy-hardware-config=3/}:S' kill >/dev/null 2>&1
 ret     = 1
 kernel_since = 6.12
 requires = core-pmu
@@ -37,7 +37,7 @@ write_backward=0
 fd=2
 group_fd=1
 
-# cache-misses
+# cache-misses, explicitly requested with the legacy hardware encoding
 type=0
 config=3
 
diff --git a/tools/perf/tests/shell/attr/test-record-group1 b/tools/perf/tests/shell/attr/test-record-group1
index a57c7ee8aaa4f9c9..5fdd5288555f32ea 100644
--- a/tools/perf/tests/shell/attr/test-record-group1
+++ b/tools/perf/tests/shell/attr/test-record-group1
@@ -1,6 +1,6 @@
 [config]
 command = record
-args    = --no-bpf-event -e '{cycles,instructions}' kill >/dev/null 2>&1
+args    = --no-bpf-event -e '{cycles,default_core/legacy-hardware-config=1/}' kill >/dev/null 2>&1
 ret     = 1
 requires = core-pmu
 
diff --git a/tools/perf/tests/shell/attr/test-record-group2 b/tools/perf/tests/shell/attr/test-record-group2
index fb6e664eeacc10ef..e7274108f5fd7e7b 100644
--- a/tools/perf/tests/shell/attr/test-record-group2
+++ b/tools/perf/tests/shell/attr/test-record-group2
@@ -1,6 +1,6 @@
 [config]
 command = record
-args    = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1
+args    = --no-bpf-event -e '{cycles/period=1234000/,default_core/legacy-hardware-config=1,period=6789000/}:S' kill >/dev/null 2>&1
 ret     = 1
 kernel_until = 6.12
 requires = core-pmu
diff --git a/tools/perf/tests/shell/attr/test-record-group3 b/tools/perf/tests/shell/attr/test-record-group3
index 7b90b4411d8f4b90..e74fd3a48ac9094f 100644
--- a/tools/perf/tests/shell/attr/test-record-group3
+++ b/tools/perf/tests/shell/attr/test-record-group3
@@ -1,6 +1,6 @@
 [config]
 command = record
-args    = --no-bpf-event -e '{cycles/period=1234000/,instructions/period=6789000/}:S' kill >/dev/null 2>&1
+args    = --no-bpf-event -e '{cycles/period=1234000/,default_core/legacy-hardware-config=1,period=6789000/}:S' kill >/dev/null 2>&1
 ret     = 1
 kernel_since = 6.12
 requires = core-pmu
diff --git a/tools/perf/tests/shell/attr/test-stat-group1 b/tools/perf/tests/shell/attr/test-stat-group1
index 3d8b44ba42d6ae19..7a31b27933f11959 100644
--- a/tools/perf/tests/shell/attr/test-stat-group1
+++ b/tools/perf/tests/shell/attr/test-stat-group1
@@ -1,6 +1,6 @@
 [config]
 command = stat
-args    = -e '{cycles,instructions}' kill >/dev/null 2>&1
+args    = -e '{cycles,default_core/legacy-hardware-config=1/}' kill >/dev/null 2>&1
 ret     = 1
 requires = core-pmu
 
-- 
2.55.0


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

* [PATCH v3 04/19] perf test attr: Fix default stat metrics expectations
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (2 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 03/19] perf test attr: Fix legacy event encodings in group tests Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:21   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 05/19] perf test attr: Relax group checking for ungrouped expectations Arnaldo Carvalho de Melo
                   ` (14 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

Since commit c7adeb0974f1 ("perf jevents: Add set of common metrics based
on default ones") and commit 2dfc0cab3da4 ("perf stat: Add detail -d,-dd,-ddd
metrics") the perf stat default metrics open their events in groups, so
group leaders and members have read_format=15 and members are enabled
without enable_on_exec, while standalone events keep read_format=3 and
are disabled until exec.

Also, since commit b1c5efbfd92e ("perf parse-events: Remove hard coded
legacy hardware and cache parsing") bare hardware events resolve to the
core PMU sysfs/JSON events (type 4), so add the raw encodings the Intel
and AMD core PMUs use for instructions, branch-misses, cpu-cycles and
stalled-cycles-frontend/backend to test-stat-default and
test-stat-detailed-1/2/3.

Add base-stat-group, that accepts both the standalone and the group
variants of read_format, disabled and enable_on_exec, and point the four
default/detailed expectations at it.  test-stat-basic, test-stat-C0 and
test-stat-no-inherit keep base-stat, whose events aren't opened in a
group: for those, a change that groups or enables them differently is
still something to catch.  test-stat-group1 does open its events in a
group, but pins read_format, disabled and enable_on_exec itself, the very
terms base-stat-group loosens, so it doesn't need it either.

Fixes: b1c5efbfd92eb84d ("perf parse-events: Remove hard coded legacy hardware and cache parsing")
Fixes: c7adeb0974f18da4 ("perf jevents: Add set of common metrics based on default ones")
Fixes: 2dfc0cab3da4a205 ("perf stat: Add detail -d,-dd,-ddd metrics")
Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/attr/base-stat-group   |  45 +++++++
 tools/perf/tests/shell/attr/test-stat-default |  90 +++++++++-----
 .../tests/shell/attr/test-stat-detailed-1     |  98 +++++++++------
 .../tests/shell/attr/test-stat-detailed-2     | 110 ++++++++++-------
 .../tests/shell/attr/test-stat-detailed-3     | 114 +++++++++++-------
 5 files changed, 307 insertions(+), 150 deletions(-)
 create mode 100644 tools/perf/tests/shell/attr/base-stat-group

diff --git a/tools/perf/tests/shell/attr/base-stat-group b/tools/perf/tests/shell/attr/base-stat-group
new file mode 100644
index 0000000000000000..9bcb8ab3687ff9fb
--- /dev/null
+++ b/tools/perf/tests/shell/attr/base-stat-group
@@ -0,0 +1,45 @@
+[event]
+fd=1
+group_fd=-1
+# 0 or PERF_FLAG_FD_CLOEXEC flag
+flags=0|8
+cpu=*
+type=0
+size=176
+config=0
+sample_period=0
+sample_type=65536
+# The default and -d/-dd/-ddd metrics open part of their events in groups:
+# the members get read_format=15 and are enabled right away, the standalone
+# ones keep read_format=3 and wait for the exec, and the leaders get 15 when
+# their group has members, 3 when alone, as create_perf_stat_counter() goes.
+read_format=3|15
+disabled=0|1
+inherit=1
+pinned=0
+exclusive=0
+exclude_user=0
+exclude_kernel=0|1
+exclude_hv=0|1
+exclude_idle=0
+mmap=0
+comm=0
+freq=0
+inherit_stat=0
+enable_on_exec=0|1
+task=0
+watermark=0
+precise_ip=0
+mmap_data=0
+sample_id_all=0
+exclude_host=0|1
+exclude_guest=0|1
+exclude_callchain_kernel=0
+exclude_callchain_user=0
+wakeup_events=0
+bp_type=0
+config1=0
+config2=0
+branch_sample_type=0
+sample_regs_user=0
+sample_stack_user=0
diff --git a/tools/perf/tests/shell/attr/test-stat-default b/tools/perf/tests/shell/attr/test-stat-default
index 74691188e859a169..6a0c0c43f23347d8 100644
--- a/tools/perf/tests/shell/attr/test-stat-default
+++ b/tools/perf/tests/shell/attr/test-stat-default
@@ -5,72 +5,72 @@ ret     = 1
 requires = core-pmu
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
-[event1:base-stat]
+[event1:base-stat-group]
 fd=1
 type=1
 config=1
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
-[event2:base-stat]
+[event2:base-stat-group]
 fd=2
 type=1
 config=3
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
-[event3:base-stat]
+[event3:base-stat-group]
 fd=3
 type=1
 config=4
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
-[event4:base-stat]
+[event4:base-stat-group]
 fd=4
 type=1
 config=2
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
-[event5:base-stat]
+[event5:base-stat-group]
 fd=5
 type=0
 config=0
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
-[event6:base-stat]
+[event6:base-stat-group]
 fd=6
 type=0
 config=7
 optional=1
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
-[event7:base-stat]
+[event7:base-stat-group]
 fd=7
 type=0
 config=8
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
-[event8:base-stat]
+[event8:base-stat-group]
 fd=8
 type=0
 config=1
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
-[event9:base-stat]
+[event9:base-stat-group]
 fd=9
 type=0
 config=4
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
-[event10:base-stat]
+[event10:base-stat-group]
 fd=10
 type=0
 config=5
 optional=1
 
 # PERF_TYPE_RAW / slots (0x400)
-[event11:base-stat]
+[event11:base-stat-group]
 fd=11
 group_fd=-1
 type=4
@@ -79,7 +79,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-retiring (0x8000)
-[event12:base-stat]
+[event12:base-stat-group]
 fd=12
 group_fd=11
 type=4
@@ -90,7 +90,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-bad-spec (0x8100)
-[event13:base-stat]
+[event13:base-stat-group]
 fd=13
 group_fd=11
 type=4
@@ -101,7 +101,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-fe-bound (0x8200)
-[event14:base-stat]
+[event14:base-stat-group]
 fd=14
 group_fd=11
 type=4
@@ -112,7 +112,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-be-bound (0x8300)
-[event15:base-stat]
+[event15:base-stat-group]
 fd=15
 group_fd=11
 type=4
@@ -123,7 +123,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400)
-[event16:base-stat]
+[event16:base-stat-group]
 fd=16
 group_fd=11
 type=4
@@ -134,7 +134,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500)
-[event17:base-stat]
+[event17:base-stat-group]
 fd=17
 group_fd=11
 type=4
@@ -145,7 +145,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600)
-[event18:base-stat]
+[event18:base-stat-group]
 fd=18
 group_fd=11
 type=4
@@ -156,7 +156,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-mem-bound (0x8700)
-[event19:base-stat]
+[event19:base-stat-group]
 fd=19
 group_fd=11
 type=4
@@ -167,71 +167,99 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
-[event20:base-stat]
+[event20:base-stat-group]
 fd=20
 type=4
 config=4109
 optional=1
 
 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/
-[event21:base-stat]
+[event21:base-stat-group]
 fd=21
 type=4
 config=17039629
 optional=1
 
-# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD
-[event22:base-stat]
+# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76)
+[event22:base-stat-group]
 fd=22
 type=4
-config=60
+config=60|118
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY
-[event23:base-stat]
+[event23:base-stat-group]
 fd=23
 type=4
 config=2097421
 optional=1
 
 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK
-[event24:base-stat]
+[event24:base-stat-group]
 fd=24
 type=4
 config=316
 optional=1
 
 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE
-[event25:base-stat]
+[event25:base-stat-group]
 fd=25
 type=4
 config=412
 optional=1
 
 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE
-[event26:base-stat]
+[event26:base-stat-group]
 fd=26
 type=4
 config=572
 optional=1
 
 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS
-[event27:base-stat]
+[event27:base-stat-group]
 fd=27
 type=4
 config=706
 optional=1
 
 # PERF_TYPE_RAW / UOPS_ISSUED.ANY
-[event28:base-stat]
+[event28:base-stat-group]
 fd=28
 type=4
 config=270
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
-[event29:base-stat]
+[event29:base-stat-group]
 fd=29
 type=4
 config=4269
+optional=1
+
+# PERF_TYPE_RAW / instructions (core PMU sysfs event)
+[event30:base-stat-group]
+fd=30
+type=4
+config=192
+optional=1
+
+# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3)
+[event31:base-stat-group]
+fd=31
+type=4
+config=197|195
+optional=1
+
+# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9)
+[event32:base-stat-group]
+fd=32
+type=4
+config=25166094|208|647|169
+optional=1
+
+# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187)
+[event33:base-stat-group]
+fd=33
+type=4
+config=25166257|25182129|209|391
 optional=1
\ No newline at end of file
diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-1 b/tools/perf/tests/shell/attr/test-stat-detailed-1
index d9a6236054f2dfb1..3d47dc6f815ff0a6 100644
--- a/tools/perf/tests/shell/attr/test-stat-detailed-1
+++ b/tools/perf/tests/shell/attr/test-stat-detailed-1
@@ -6,73 +6,73 @@ requires = core-pmu
 
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
-[event1:base-stat]
+[event1:base-stat-group]
 fd=1
 type=1
 config=1
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
-[event2:base-stat]
+[event2:base-stat-group]
 fd=2
 type=1
 config=3
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
-[event3:base-stat]
+[event3:base-stat-group]
 fd=3
 type=1
 config=4
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
-[event4:base-stat]
+[event4:base-stat-group]
 fd=4
 type=1
 config=2
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
-[event5:base-stat]
+[event5:base-stat-group]
 fd=5
 type=0
 config=0
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
-[event6:base-stat]
+[event6:base-stat-group]
 fd=6
 type=0
 config=7
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
-[event7:base-stat]
+[event7:base-stat-group]
 fd=7
 type=0
 config=8
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
-[event8:base-stat]
+[event8:base-stat-group]
 fd=8
 type=0
 config=1
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
-[event9:base-stat]
+[event9:base-stat-group]
 fd=9
 type=0
 config=4
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
-[event10:base-stat]
+[event10:base-stat-group]
 fd=10
 type=0
 config=5
 optional=1
 
 # PERF_TYPE_RAW / slots (0x400)
-[event11:base-stat]
+[event11:base-stat-group]
 fd=11
 group_fd=-1
 type=4
@@ -81,7 +81,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-retiring (0x8000)
-[event12:base-stat]
+[event12:base-stat-group]
 fd=12
 group_fd=11
 type=4
@@ -92,7 +92,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-bad-spec (0x8100)
-[event13:base-stat]
+[event13:base-stat-group]
 fd=13
 group_fd=11
 type=4
@@ -103,7 +103,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-fe-bound (0x8200)
-[event14:base-stat]
+[event14:base-stat-group]
 fd=14
 group_fd=11
 type=4
@@ -114,7 +114,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-be-bound (0x8300)
-[event15:base-stat]
+[event15:base-stat-group]
 fd=15
 group_fd=11
 type=4
@@ -125,7 +125,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400)
-[event16:base-stat]
+[event16:base-stat-group]
 fd=16
 group_fd=11
 type=4
@@ -136,7 +136,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500)
-[event17:base-stat]
+[event17:base-stat-group]
 fd=17
 group_fd=11
 type=4
@@ -147,7 +147,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600)
-[event18:base-stat]
+[event18:base-stat-group]
 fd=18
 group_fd=11
 type=4
@@ -158,7 +158,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-mem-bound (0x8700)
-[event19:base-stat]
+[event19:base-stat-group]
 fd=19
 group_fd=11
 type=4
@@ -169,63 +169,63 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
-[event20:base-stat]
+[event20:base-stat-group]
 fd=20
 type=4
 config=4109
 optional=1
 
 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/
-[event21:base-stat]
+[event21:base-stat-group]
 fd=21
 type=4
 config=17039629
 optional=1
 
-# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD
-[event22:base-stat]
+# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76)
+[event22:base-stat-group]
 fd=22
 type=4
-config=60
+config=60|118
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY
-[event23:base-stat]
+[event23:base-stat-group]
 fd=23
 type=4
 config=2097421
 optional=1
 
 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK
-[event24:base-stat]
+[event24:base-stat-group]
 fd=24
 type=4
 config=316
 optional=1
 
 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE
-[event25:base-stat]
+[event25:base-stat-group]
 fd=25
 type=4
 config=412
 optional=1
 
 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE
-[event26:base-stat]
+[event26:base-stat-group]
 fd=26
 type=4
 config=572
 optional=1
 
 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS
-[event27:base-stat]
+[event27:base-stat-group]
 fd=27
 type=4
 config=706
 optional=1
 
 # PERF_TYPE_RAW / UOPS_ISSUED.ANY
-[event28:base-stat]
+[event28:base-stat-group]
 fd=28
 type=4
 config=270
@@ -235,7 +235,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1D                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event29:base-stat]
+[event29:base-stat-group]
 fd=29
 type=3
 config=0
@@ -245,7 +245,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1D                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event30:base-stat]
+[event30:base-stat-group]
 fd=30
 type=3
 config=65536
@@ -255,7 +255,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_LL                 <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event31:base-stat]
+[event31:base-stat-group]
 fd=31
 type=3
 config=2
@@ -265,15 +265,43 @@ optional=1
 #  PERF_COUNT_HW_CACHE_LL                 <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event32:base-stat]
+[event32:base-stat-group]
 fd=32
 type=3
 config=65538
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
-[event33:base-stat]
+[event33:base-stat-group]
 fd=33
 type=4
 config=4269
+optional=1
+
+# PERF_TYPE_RAW / instructions (core PMU sysfs event)
+[event34:base-stat-group]
+fd=34
+type=4
+config=192
+optional=1
+
+# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3)
+[event35:base-stat-group]
+fd=35
+type=4
+config=197|195
+optional=1
+
+# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9)
+[event36:base-stat-group]
+fd=36
+type=4
+config=25166094|208|647|169
+optional=1
+
+# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187)
+[event37:base-stat-group]
+fd=37
+type=4
+config=25166257|25182129|209|391
 optional=1
\ No newline at end of file
diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-2 b/tools/perf/tests/shell/attr/test-stat-detailed-2
index 582a30289ccfa6df..ebe1f9e2736ddd68 100644
--- a/tools/perf/tests/shell/attr/test-stat-detailed-2
+++ b/tools/perf/tests/shell/attr/test-stat-detailed-2
@@ -6,73 +6,73 @@ requires = core-pmu
 
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
-[event1:base-stat]
+[event1:base-stat-group]
 fd=1
 type=1
 config=1
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
-[event2:base-stat]
+[event2:base-stat-group]
 fd=2
 type=1
 config=3
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
-[event3:base-stat]
+[event3:base-stat-group]
 fd=3
 type=1
 config=4
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
-[event4:base-stat]
+[event4:base-stat-group]
 fd=4
 type=1
 config=2
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
-[event5:base-stat]
+[event5:base-stat-group]
 fd=5
 type=0
 config=0
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
-[event6:base-stat]
+[event6:base-stat-group]
 fd=6
 type=0
 config=7
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
-[event7:base-stat]
+[event7:base-stat-group]
 fd=7
 type=0
 config=8
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
-[event8:base-stat]
+[event8:base-stat-group]
 fd=8
 type=0
 config=1
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
-[event9:base-stat]
+[event9:base-stat-group]
 fd=9
 type=0
 config=4
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
-[event10:base-stat]
+[event10:base-stat-group]
 fd=10
 type=0
 config=5
 optional=1
 
 # PERF_TYPE_RAW / slots (0x400)
-[event11:base-stat]
+[event11:base-stat-group]
 fd=11
 group_fd=-1
 type=4
@@ -81,7 +81,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-retiring (0x8000)
-[event12:base-stat]
+[event12:base-stat-group]
 fd=12
 group_fd=11
 type=4
@@ -92,7 +92,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-bad-spec (0x8100)
-[event13:base-stat]
+[event13:base-stat-group]
 fd=13
 group_fd=11
 type=4
@@ -103,7 +103,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-fe-bound (0x8200)
-[event14:base-stat]
+[event14:base-stat-group]
 fd=14
 group_fd=11
 type=4
@@ -114,7 +114,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-be-bound (0x8300)
-[event15:base-stat]
+[event15:base-stat-group]
 fd=15
 group_fd=11
 type=4
@@ -125,7 +125,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400)
-[event16:base-stat]
+[event16:base-stat-group]
 fd=16
 group_fd=11
 type=4
@@ -136,7 +136,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500)
-[event17:base-stat]
+[event17:base-stat-group]
 fd=17
 group_fd=11
 type=4
@@ -147,7 +147,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600)
-[event18:base-stat]
+[event18:base-stat-group]
 fd=18
 group_fd=11
 type=4
@@ -158,7 +158,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-mem-bound (0x8700)
-[event19:base-stat]
+[event19:base-stat-group]
 fd=19
 group_fd=11
 type=4
@@ -169,63 +169,63 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
-[event20:base-stat]
+[event20:base-stat-group]
 fd=20
 type=4
 config=4109
 optional=1
 
 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/
-[event21:base-stat]
+[event21:base-stat-group]
 fd=21
 type=4
 config=17039629
 optional=1
 
-# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD
-[event22:base-stat]
+# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76)
+[event22:base-stat-group]
 fd=22
 type=4
-config=60
+config=60|118
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY
-[event23:base-stat]
+[event23:base-stat-group]
 fd=23
 type=4
 config=2097421
 optional=1
 
 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK
-[event24:base-stat]
+[event24:base-stat-group]
 fd=24
 type=4
 config=316
 optional=1
 
 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE
-[event25:base-stat]
+[event25:base-stat-group]
 fd=25
 type=4
 config=412
 optional=1
 
 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE
-[event26:base-stat]
+[event26:base-stat-group]
 fd=26
 type=4
 config=572
 optional=1
 
 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS
-[event27:base-stat]
+[event27:base-stat-group]
 fd=27
 type=4
 config=706
 optional=1
 
 # PERF_TYPE_RAW / UOPS_ISSUED.ANY
-[event28:base-stat]
+[event28:base-stat-group]
 fd=28
 type=4
 config=270
@@ -235,7 +235,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1D                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event29:base-stat]
+[event29:base-stat-group]
 fd=29
 type=3
 config=0
@@ -245,7 +245,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1D                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event30:base-stat]
+[event30:base-stat-group]
 fd=30
 type=3
 config=65536
@@ -255,7 +255,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_LL                 <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event31:base-stat]
+[event31:base-stat-group]
 fd=31
 type=3
 config=2
@@ -265,7 +265,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_LL                 <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event32:base-stat]
+[event32:base-stat-group]
 fd=32
 type=3
 config=65538
@@ -275,7 +275,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1I                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event33:base-stat]
+[event33:base-stat-group]
 fd=33
 type=3
 config=1
@@ -285,7 +285,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1I                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event34:base-stat]
+[event34:base-stat-group]
 fd=34
 type=3
 config=65537
@@ -295,7 +295,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_DTLB               <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event35:base-stat]
+[event35:base-stat-group]
 fd=35
 type=3
 config=3
@@ -305,7 +305,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_DTLB               <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event36:base-stat]
+[event36:base-stat-group]
 fd=36
 type=3
 config=65539
@@ -315,7 +315,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_ITLB               <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event37:base-stat]
+[event37:base-stat-group]
 fd=37
 type=3
 config=4
@@ -325,15 +325,43 @@ optional=1
 #  PERF_COUNT_HW_CACHE_ITLB               <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event38:base-stat]
+[event38:base-stat-group]
 fd=38
 type=3
 config=65540
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
-[event39:base-stat]
+[event39:base-stat-group]
 fd=39
 type=4
 config=4269
+optional=1
+
+# PERF_TYPE_RAW / instructions (core PMU sysfs event)
+[event40:base-stat-group]
+fd=40
+type=4
+config=192
+optional=1
+
+# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3)
+[event41:base-stat-group]
+fd=41
+type=4
+config=197|195
+optional=1
+
+# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9)
+[event42:base-stat-group]
+fd=42
+type=4
+config=25166094|208|647|169
+optional=1
+
+# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187)
+[event43:base-stat-group]
+fd=43
+type=4
+config=25166257|25182129|209|391
 optional=1
\ No newline at end of file
diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-3 b/tools/perf/tests/shell/attr/test-stat-detailed-3
index 2d0fca8b5020f504..e7eef828245c5d6c 100644
--- a/tools/perf/tests/shell/attr/test-stat-detailed-3
+++ b/tools/perf/tests/shell/attr/test-stat-detailed-3
@@ -6,73 +6,73 @@ requires = core-pmu
 
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
-[event1:base-stat]
+[event1:base-stat-group]
 fd=1
 type=1
 config=1
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
-[event2:base-stat]
+[event2:base-stat-group]
 fd=2
 type=1
 config=3
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
-[event3:base-stat]
+[event3:base-stat-group]
 fd=3
 type=1
 config=4
 
 # PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
-[event4:base-stat]
+[event4:base-stat-group]
 fd=4
 type=1
 config=2
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
-[event5:base-stat]
+[event5:base-stat-group]
 fd=5
 type=0
 config=0
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
-[event6:base-stat]
+[event6:base-stat-group]
 fd=6
 type=0
 config=7
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
-[event7:base-stat]
+[event7:base-stat-group]
 fd=7
 type=0
 config=8
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
-[event8:base-stat]
+[event8:base-stat-group]
 fd=8
 type=0
 config=1
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
-[event9:base-stat]
+[event9:base-stat-group]
 fd=9
 type=0
 config=4
 optional=1
 
 # PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
-[event10:base-stat]
+[event10:base-stat-group]
 fd=10
 type=0
 config=5
 optional=1
 
 # PERF_TYPE_RAW / slots (0x400)
-[event11:base-stat]
+[event11:base-stat-group]
 fd=11
 group_fd=-1
 type=4
@@ -81,7 +81,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-retiring (0x8000)
-[event12:base-stat]
+[event12:base-stat-group]
 fd=12
 group_fd=11
 type=4
@@ -92,7 +92,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-bad-spec (0x8100)
-[event13:base-stat]
+[event13:base-stat-group]
 fd=13
 group_fd=11
 type=4
@@ -103,7 +103,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-fe-bound (0x8200)
-[event14:base-stat]
+[event14:base-stat-group]
 fd=14
 group_fd=11
 type=4
@@ -114,7 +114,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-be-bound (0x8300)
-[event15:base-stat]
+[event15:base-stat-group]
 fd=15
 group_fd=11
 type=4
@@ -125,7 +125,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-heavy-ops (0x8400)
-[event16:base-stat]
+[event16:base-stat-group]
 fd=16
 group_fd=11
 type=4
@@ -136,7 +136,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-br-mispredict (0x8500)
-[event17:base-stat]
+[event17:base-stat-group]
 fd=17
 group_fd=11
 type=4
@@ -147,7 +147,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-fetch-lat (0x8600)
-[event18:base-stat]
+[event18:base-stat-group]
 fd=18
 group_fd=11
 type=4
@@ -158,7 +158,7 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / topdown-mem-bound (0x8700)
-[event19:base-stat]
+[event19:base-stat-group]
 fd=19
 group_fd=11
 type=4
@@ -169,63 +169,63 @@ read_format=15
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
-[event20:base-stat]
+[event20:base-stat-group]
 fd=20
 type=4
 config=4109
 optional=1
 
 # PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/
-[event21:base-stat]
+[event21:base-stat-group]
 fd=21
 type=4
 config=17039629
 optional=1
 
-# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD
-[event22:base-stat]
+# PERF_TYPE_RAW / cpu-cycles (core PMU sysfs event; Intel 0x3c, AMD 0x76)
+[event22:base-stat-group]
 fd=22
 type=4
-config=60
+config=60|118
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY
-[event23:base-stat]
+[event23:base-stat-group]
 fd=23
 type=4
 config=2097421
 optional=1
 
 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK
-[event24:base-stat]
+[event24:base-stat-group]
 fd=24
 type=4
 config=316
 optional=1
 
 # PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE
-[event25:base-stat]
+[event25:base-stat-group]
 fd=25
 type=4
 config=412
 optional=1
 
 # PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE
-[event26:base-stat]
+[event26:base-stat-group]
 fd=26
 type=4
 config=572
 optional=1
 
 # PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS
-[event27:base-stat]
+[event27:base-stat-group]
 fd=27
 type=4
 config=706
 optional=1
 
 # PERF_TYPE_RAW / UOPS_ISSUED.ANY
-[event28:base-stat]
+[event28:base-stat-group]
 fd=28
 type=4
 config=270
@@ -235,7 +235,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1D                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event29:base-stat]
+[event29:base-stat-group]
 fd=29
 type=3
 config=0
@@ -245,7 +245,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1D                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event30:base-stat]
+[event30:base-stat-group]
 fd=30
 type=3
 config=65536
@@ -255,7 +255,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_LL                 <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event31:base-stat]
+[event31:base-stat-group]
 fd=31
 type=3
 config=2
@@ -265,7 +265,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_LL                 <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event32:base-stat]
+[event32:base-stat-group]
 fd=32
 type=3
 config=65538
@@ -275,7 +275,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1I                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event33:base-stat]
+[event33:base-stat-group]
 fd=33
 type=3
 config=1
@@ -285,7 +285,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1I                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event34:base-stat]
+[event34:base-stat-group]
 fd=34
 type=3
 config=65537
@@ -295,7 +295,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_DTLB               <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event35:base-stat]
+[event35:base-stat-group]
 fd=35
 type=3
 config=3
@@ -305,7 +305,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_DTLB               <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event36:base-stat]
+[event36:base-stat-group]
 fd=36
 type=3
 config=65539
@@ -315,7 +315,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_ITLB               <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event37:base-stat]
+[event37:base-stat-group]
 fd=37
 type=3
 config=4
@@ -325,7 +325,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_ITLB               <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_READ            <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event38:base-stat]
+[event38:base-stat-group]
 fd=38
 type=3
 config=65540
@@ -335,7 +335,7 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1D                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_PREFETCH        <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_ACCESS      << 16)
-[event39:base-stat]
+[event39:base-stat-group]
 fd=39
 type=3
 config=512
@@ -345,15 +345,43 @@ optional=1
 #  PERF_COUNT_HW_CACHE_L1D                <<  0  |
 # (PERF_COUNT_HW_CACHE_OP_PREFETCH        <<  8) |
 # (PERF_COUNT_HW_CACHE_RESULT_MISS        << 16)
-[event40:base-stat]
+[event40:base-stat-group]
 fd=40
 type=3
 config=66048
 optional=1
 
 # PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
-[event41:base-stat]
+[event41:base-stat-group]
 fd=41
 type=4
 config=4269
+optional=1
+
+# PERF_TYPE_RAW / instructions (core PMU sysfs event)
+[event42:base-stat-group]
+fd=42
+type=4
+config=192
+optional=1
+
+# PERF_TYPE_RAW / branch-misses (core PMU sysfs event; Intel 0xc5, AMD 0xc3)
+[event43:base-stat-group]
+fd=43
+type=4
+config=197|195
+optional=1
+
+# PERF_TYPE_RAW / stalled-cycles-frontend (core PMU sysfs event; Intel 0x180010e, AMD 0xd0/0x287/0xa9)
+[event44:base-stat-group]
+fd=44
+type=4
+config=25166094|208|647|169
+optional=1
+
+# PERF_TYPE_RAW / stalled-cycles-backend (core PMU sysfs event; Intel 0x18001b1/0x1803fb1, AMD 0xd1/0x187)
+[event45:base-stat-group]
+fd=45
+type=4
+config=25166257|25182129|209|391
 optional=1
\ No newline at end of file
-- 
2.55.0


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

* [PATCH v3 05/19] perf test attr: Relax group checking for ungrouped expectations
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (3 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 04/19] perf test attr: Fix default stat metrics expectations Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:23   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 06/19] perf evsel: Mark the event as supported when retrying an open Arnaldo Carvalho de Melo
                   ` (13 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

When matching the recorded events back against the expectations,
attr.py requires every event opened in a group to match an expectation
that declares the same group.  The perf stat default metrics open the
events of each metric in groups, but the test expectations only verify
the event attributes, so test-stat-default fails with:

  FAILED '.../test-stat-default' - group failure

Only enforce the group of a recorded event when the expectation it
matches declares one.  Expectations that ask for a group are still
verified in both directions.

Fixes: c7adeb0974f18da4 ("perf jevents: Add set of common metrics based on default ones")
Fixes: 2dfc0cab3da4a205 ("perf stat: Add detail -d,-dd,-ddd metrics")
Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/lib/attr.py | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
index 2d7dd26a5737777c..087bfa4532b69c58 100644
--- a/tools/perf/tests/shell/lib/attr.py
+++ b/tools/perf/tests/shell/lib/attr.py
@@ -372,7 +372,7 @@ class Test(object):
             else:
                 raise Unsup(self)
 
-    def compare(self, expect, result):
+    def compare(self, expect, result, strict_groups=True):
         match = {}
 
         log.debug("  compare")
@@ -406,8 +406,9 @@ class Test(object):
 
             match[exp_name] = exp_list
 
-        # For each defined group in the expected events
-        # check we match the same group in the result.
+        # Check that each expected group is matched in the result; an event
+        # opened in a group may match an expectation that doesn't declare
+        # one, as those only verify the event attributes.
         for exp_name, exp_event in expect.items():
             group = exp_event.group
 
@@ -416,6 +417,8 @@ class Test(object):
 
             for res_name in match[exp_name]:
                 res_group = result[res_name].group
+                if res_group == '' and not strict_groups:
+                    continue
                 if res_group not in match[group]:
                     raise Fail(self, 'group failure')
 
@@ -454,7 +457,7 @@ class Test(object):
 
             # do the expectation - results matching - both ways
             self.compare(self.expect, self.result)
-            self.compare(self.result, self.expect)
+            self.compare(self.result, self.expect, strict_groups=False)
 
         finally:
             # cleanup
-- 
2.55.0


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

* [PATCH v3 06/19] perf evsel: Mark the event as supported when retrying an open
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (4 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 05/19] perf test attr: Relax group checking for ungrouped expectations Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:25   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 07/19] perf test attr: Ignore the core PMU type perf puts in 'config' Arnaldo Carvalho de Melo
                   ` (12 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

Opening a group with perf record on a machine with perf_event_paranoid >
1 and no CAP_PERFMON fails the first perf_event_open() with EACCES, after
which evsel__open_cpu() clears evsel->supported; evsel__fallback() then
adds exclude_kernel and record__open() retries, and the retry succeeds
with supported left false.  get_group_fd() treats unsupported events as
ungroupable, so the retry opens the group members without their leader:

  sys_perf_event_open: pid 12345 cpu 0 group_fd -1
  FAILED '.../test-record-group-sampling1' - group failure

which fails the group subtests of "perf test 82", the Perf attribute
expectations test, when it is run as a regular user, while it passes as
root or with CAP_PERFMON, where the first open doesn't fail.

commit 2cc7aa995ce9 ("perf stat: Refactor retry/skip/fatal error handling")
dropped the pos->supported = true that record__open() did after a
successful open and moved the resets into the stat code only, while
saying "When an event is tried the supported is set to true again".  Do
that in evsel__open_cpu(), which every open entry point funnels through,
so that a retry describes its own try instead of the previous failure.

Fixes: 2cc7aa995ce9ecd8 ("perf stat: Refactor retry/skip/fatal error handling")
Cc: Ian Rogers <irogers@google.com>
Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/util/evsel.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 9c5e7510f0c019c3..e61f309e93dccc7f 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3014,6 +3014,12 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus,
 	enum rlimit_action set_rlimit = NO_CHANGE;
 	struct perf_cpu cpu;
 
+	/*
+	 * A failed attempt being retried after evsel__fallback() would
+	 * otherwise keep group members out of their group in get_group_fd().
+	 */
+	evsel->supported = true;
+
 	if (evsel__is_retire_lat(evsel)) {
 		err = evsel__tpebs_open(evsel);
 		goto out;
-- 
2.55.0


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

* [PATCH v3 07/19] perf test attr: Ignore the core PMU type perf puts in 'config'
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (5 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 06/19] perf evsel: Mark the event as supported when retrying an open Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:24   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 08/19] perf evsel: Don't dump probe events to PERF_TEST_ATTR Arnaldo Carvalho de Melo
                   ` (11 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

Since commit 55bcf6ef314a ("perf: Extend PERF_TYPE_HARDWARE and
PERF_TYPE_HW_CACHE") in v5.13, and arm_pmu opted in with commit
5c816728651a ("arm_pmu: Add PERF_PMU_CAP_EXTENDED_HW_TYPE capability")
in v6.6, the kernel can be told which core PMU a legacy type event is
meant for, with its type in the top 32 bits of 'config', see
perf_pmus__supports_extended_type(), which __parse_events_add_numeric()
does for HARDWARE and HW_CACHE events, but only when there is more than
one core PMU, i.e. on heterogeneous machines, where asking for one event
ends up being one event per core PMU:

  $ perf stat -e cycles -a -- sleep 1
     20,520,998      armv8_cortex_a53/cycles/
     13,029,350      armv8_cortex_a73/cycles/

and what lands in PERF_TEST_ATTR is the extended encoding:

  type=0
  config=38654705664    # core PMU type 9, PERF_COUNT_HW_CPU_CYCLES

so the expectations written for the single core PMU case fail with:

  expected config=0|1, got 38654705664
  expected config=0, got 38654705664    # 'perf stat -d' L1-dcache events

which is what now happens on an Amlogic A311D (2 Cortex-A53 + 4
Cortex-A73), a machine that just got the CPU PMU nodes it was missing.

Take those bits out when comparing the 'config' of legacy type events,
keeping what these expectations mean on machines with a single core PMU:
which of the PMUs is counting is not what we're asking here.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/lib/attr.py | 53 ++++++++++++++++++++++++++++--
 1 file changed, 51 insertions(+), 2 deletions(-)

diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
index 087bfa4532b69c58..fb866fb75175dc95 100644
--- a/tools/perf/tests/shell/lib/attr.py
+++ b/tools/perf/tests/shell/lib/attr.py
@@ -73,6 +73,32 @@ def core_pmu_probe():
 
     return True
 
+PERF_TYPE_HARDWARE = 0
+PERF_TYPE_HW_CACHE = 3
+
+# The legacy event types, where perf may qualify 'config' with the core PMU
+# type in its top 32 bits when there is more than one core PMU.
+LEGACY_TYPES = (str(PERF_TYPE_HARDWARE), str(PERF_TYPE_HW_CACHE))
+
+def hide_core_pmu_type(value):
+    """
+    The kernel can be told which core PMU a legacy type event is meant for by
+    putting its type in the top 32 bits of 'config', which perf only does when
+    there is more than one core PMU, so take those bits out to keep the
+    expectations written for the single core PMU case matching on
+    heterogeneous machines.
+    """
+    hidden = []
+
+    for alternative in value.split('|'):
+        try:
+            alternative = str(int(alternative, 0) & 0xffffffff)
+        except ValueError:
+            pass
+        hidden.append(alternative)
+
+    return '|'.join(hidden)
+
 class Fail(Exception):
     def __init__(self, test, msg):
         self.msg = msg
@@ -146,12 +172,33 @@ class Event(dict):
         self.add(base)
         self.add(data)
 
+    def hide_core_pmu_type_terms(self, other):
+        """
+        Is this a legacy type event? Then the core PMU type that perf may have
+        put in the top 32 bits of 'config' to tell the kernel which core PMU it
+        wants isn't part of what we asked for, see hide_core_pmu_type().
+        """
+        for event in (self, other):
+            for type in event.get('type', '').split('|'):
+                if type in LEGACY_TYPES:
+                    return True
+
+        return False
+
+    def hide_core_pmu_type(self, term, value, other):
+        if term == 'config' and self.hide_core_pmu_type_terms(other):
+            return hide_core_pmu_type(value)
+
+        return value
+
     def equal(self, other):
         for t in Event.terms:
             log.debug("      [%s] %s %s", t, self[t], other[t])
             if t not in self or t not in other:
                 return False
-            if not data_equal(self[t], other[t]):
+            exp = self.hide_core_pmu_type(t, self[t], other)
+            got = self.hide_core_pmu_type(t, other[t], other)
+            if not data_equal(exp, got):
                 return False
         return True
 
@@ -164,7 +211,9 @@ class Event(dict):
         for t in Event.terms:
             if t not in self or t not in other:
                 continue
-            if not data_equal(self[t], other[t]):
+            exp = self.hide_core_pmu_type(t, self[t], other)
+            got = self.hide_core_pmu_type(t, other[t], other)
+            if not data_equal(exp, got):
                 log.warning("expected %s=%s, got %s", t, self[t], other[t])
 
 def parse_version(version):
-- 
2.55.0


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

* [PATCH v3 08/19] perf evsel: Don't dump probe events to PERF_TEST_ATTR
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (6 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 07/19] perf test attr: Ignore the core PMU type perf puts in 'config' Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:31   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 09/19] perf test attr: Introduce mask_bits(), generalizing hide_core_pmu_type() Arnaldo Carvalho de Melo
                   ` (10 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

Events opened just to ask if they are supported, i.e. the ones in
is_event_supported() and is_libpfm_event_supported(), go through the
regular evsel__open() path and thus end up in the event files written to
$PERF_TEST_ATTR, with everything but type, config and disabled zeroed:

  [event-0-38654705664-3]
  fd=3
  cpu=-1
  pid=0
  type=0
  size=0
  config=38654705664
  sample_type=0
  ...

which 'perf test attr' then takes as one of the events that were asked
for, failing with:

  expected cpu=0, got -1
  expected size=176, got 0
  expected sample_type=65927, got 0
  expected freq=1, got 0

This started showing up when there is more than one core PMU, as
perf_pmus__supports_extended_type() probes one such event per core PMU,
but these records were always bogus: nothing asked for them.

Flag those evsels as support probes, keeping the name clear of the
kprobe/uprobe meaning probe already has in evsel, and leave them out of
the dump.

Reported-by: Namhyung Kim <namhyung@kernel.org>
Link: https://lore.kernel.org/linux-perf-users/asWQvXRdqp_RK85c@google.com/
Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/util/evsel.c        | 6 +++++-
 tools/perf/util/evsel.h        | 1 +
 tools/perf/util/pfm.c          | 1 +
 tools/perf/util/print-events.c | 1 +
 4 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index e61f309e93dccc7f..59318fef03cb17ff 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -3104,7 +3104,11 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus,
 
 			bpf_counter__install_pe(evsel, idx, fd);
 
-			if (unlikely(test_attr__enabled())) {
+			/*
+			 * Support probes aren't what was asked for, leave them
+			 * out of the PERF_TEST_ATTR dump.
+			 */
+			if (unlikely(test_attr__enabled() && !evsel->support_probe)) {
 				test_attr__open(&evsel->core.attr, pid, cpu,
 						fd, group_fd, evsel->open_flags);
 			}
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index 174f3414fd3cfa0d..53f3641b66e18674 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -120,6 +120,7 @@ struct evsel {
 	u64			db_id;
 	bool			uniquified_name;
 	bool 			supported;
+	bool			support_probe;	/* Opened just to check for support. */
 	bool 			needs_swap;
 	bool 			disabled;
 	bool			no_aux_samples;
diff --git a/tools/perf/util/pfm.c b/tools/perf/util/pfm.c
index f80d6b0df47ae64d..ad9bc481da70604e 100644
--- a/tools/perf/util/pfm.c
+++ b/tools/perf/util/pfm.c
@@ -141,6 +141,7 @@ static bool is_libpfm_event_supported(const char *name, struct perf_cpu_map *cpu
 		return false;
 
 	evsel->is_libpfm_event = true;
+	evsel->support_probe = true;
 
 	ret = evsel__open(evsel, cpus, threads);
 	if (ret == -EACCES) {
diff --git a/tools/perf/util/print-events.c b/tools/perf/util/print-events.c
index 0242243681b6b6f9..77428d9e73c4f1b6 100644
--- a/tools/perf/util/print-events.c
+++ b/tools/perf/util/print-events.c
@@ -147,6 +147,7 @@ bool is_event_supported(u8 type, u64 config)
 
 	evsel = evsel__new(&attr);
 	if (evsel) {
+		evsel->support_probe = true;
 		ret = evsel__open(evsel, NULL, tmap) >= 0;
 
 		if (!ret) {
-- 
2.55.0


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

* [PATCH v3 09/19] perf test attr: Introduce mask_bits(), generalizing hide_core_pmu_type()
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (7 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 08/19] perf evsel: Don't dump probe events to PERF_TEST_ATTR Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:23   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 10/19] perf test attr: Accept either sample id bit perf asks for Arnaldo Carvalho de Melo
                   ` (9 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

What hide_core_pmu_type() does is take a set of bits out of each of the
'|'-separated alternatives in a value, wildcards included, keeping the
rest of the expression: extract that as mask_bits() and implement
hide_core_pmu_type() in terms of it, a prep for using the same thing to
ignore, when comparing expectations, the sample id bit perf ends up
asking for and the read_format group bit that a PMU count can remove.

No change in behaviour.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/lib/attr.py | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
index fb866fb75175dc95..ee55fbe6f07bd7b5 100644
--- a/tools/perf/tests/shell/lib/attr.py
+++ b/tools/perf/tests/shell/lib/attr.py
@@ -80,24 +80,31 @@ PERF_TYPE_HW_CACHE = 3
 # type in its top 32 bits when there is more than one core PMU.
 LEGACY_TYPES = (str(PERF_TYPE_HARDWARE), str(PERF_TYPE_HW_CACHE))
 
-def hide_core_pmu_type(value):
+def mask_bits(value, bits):
     """
-    The kernel can be told which core PMU a legacy type event is meant for by
-    putting its type in the top 32 bits of 'config', which perf only does when
-    there is more than one core PMU, so take those bits out to keep the
-    expectations written for the single core PMU case matching on
-    heterogeneous machines.
+    Take the bits in 'bits' out of each of the '|'-separated alternatives in
+    'value', keeping the rest of the expression, wildcards included.
     """
-    hidden = []
+    masked = []
 
     for alternative in value.split('|'):
         try:
-            alternative = str(int(alternative, 0) & 0xffffffff)
+            alternative = str(int(alternative, 0) & ~bits)
         except ValueError:
             pass
-        hidden.append(alternative)
+        masked.append(alternative)
 
-    return '|'.join(hidden)
+    return '|'.join(masked)
+
+def hide_core_pmu_type(value):
+    """
+    The kernel can be told which core PMU a legacy type event is meant for by
+    putting its type in the top 32 bits of 'config', which perf only does when
+    there is more than one core PMU, so take those bits out to keep the
+    expectations written for the single core PMU case matching on
+    heterogeneous machines.
+    """
+    return mask_bits(value, 0xffffffff00000000)
 
 class Fail(Exception):
     def __init__(self, test, msg):
-- 
2.55.0


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

* [PATCH v3 10/19] perf test attr: Accept either sample id bit perf asks for
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (8 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 09/19] perf test attr: Introduce mask_bits(), generalizing hide_core_pmu_type() Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:28   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 11/19] perf test attr: Ignore the dummy tracking event and the terms it carries Arnaldo Carvalho de Melo
                   ` (8 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

evlist__config() asks for a way to tell the samples apart,
PERF_SAMPLE_IDENTIFIER where available, PERF_SAMPLE_ID otherwise:

  bool sample_id = opts->sample_id;
  ...
  if (opts->full_auxtrace || opts->sample_identifier) {
        ...
        use_sample_identifier = perf_can_sample_identifier();
        sample_id = true;
  } else if (evlist__nr_entries(evlist) > 1) {
        ...
        use_sample_identifier = perf_can_sample_identifier();
        sample_id = true;
  }

so the id is asked for whenever the evlist has more than one event, and
also when asked to outright, with --sample-identifier or by an auxtrace
session, or by record__config() for a pipe run, through opts->sample_id,
none of which depends on the sample types: 'perf record
--sample-identifier -e cycles' and the arm SPE expectations in this very
directory ask for it with a single event.

Which one it asks for depends on the composition of the evlist: with
more than one core PMU a request for 'cycles' becomes one event per core
PMU, the extra dummy tracking events then make the sample types differ and
perf goes for IDENTIFIER, while the expectations were written on machines
with a single core PMU, where perf asks for ID:

  expected sample_type=263, got 65799    # 263 | (1 << 16)
  expected sample_type=343, got 65815    # (343 & ~(1 << 6)) | (1 << 16), group-sampling

Both bits mean the same thing here, how the samples get associated with the
event that produced them, so when the expectation asks for one of them, ask
for "an id of some sort" on both sides, and when the expectation doesn't
ask for any, take the bits out of what got recorded: whether they are there
and which one it is are consequences of what was asked for and of how the
request got expanded, none of that is what these expectations are about.

Since Test.compare() looks at both directions, expectations matching the
recorded events and recorded events matching the expectations, keep track
of which side is the one perf recorded to take the bits out of the right
side.

The masking is only for the comparison: the warnings keep showing the
values as they are, nothing to un-mask by hand to see what is being
complained about.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/lib/attr.py | 82 ++++++++++++++++++++++++++++--
 1 file changed, 77 insertions(+), 5 deletions(-)

diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
index ee55fbe6f07bd7b5..da4614068772eaec 100644
--- a/tools/perf/tests/shell/lib/attr.py
+++ b/tools/perf/tests/shell/lib/attr.py
@@ -75,6 +75,8 @@ def core_pmu_probe():
 
 PERF_TYPE_HARDWARE = 0
 PERF_TYPE_HW_CACHE = 3
+PERF_SAMPLE_ID = 1 << 6
+PERF_SAMPLE_IDENTIFIER = 1 << 16
 
 # The legacy event types, where perf may qualify 'config' with the core PMU
 # type in its top 32 bits when there is more than one core PMU.
@@ -106,6 +108,49 @@ def hide_core_pmu_type(value):
     """
     return mask_bits(value, 0xffffffff00000000)
 
+# The two sample id bits mean the same thing here: how the samples of the
+# events in the evlist get associated with the event that produced them.
+SAMPLE_ID_BITS = PERF_SAMPLE_ID | PERF_SAMPLE_IDENTIFIER
+
+# No real sample_type bit is this high, so it can stand for "either of the
+# sample id bits" when comparing the two sides.
+SAMPLE_ID_EITHER = 1 << 40
+
+def hide_sample_id(expected, recorded):
+    """
+    perf asks for a way to tell the samples apart, PERF_SAMPLE_IDENTIFIER where
+    available, PERF_SAMPLE_ID otherwise, see evlist__config(): whenever the
+    evlist has more than one event, and when asked to outright, with
+    --sample-identifier, by an auxtrace session, or by record__config() for a
+    pipe run, none of which depends on the sample types, as those only choose
+    between the two bits:
+    with more than one core PMU the dummy tracking events make the sample types
+    differ, so IDENTIFIER is asked for even for events whose expectations ask
+    for ID, written on a machine with a single core PMU.
+
+    So treat the two as the same thing: when the expectation asks for either
+    of them, ask for "an id of some sort" on both sides, and when the
+    expectation doesn't ask for any, take the bits out of what got recorded.
+    """
+    try:
+        asked = int(expected, 0) & SAMPLE_ID_BITS
+    except ValueError:
+        # The expectation is a wildcard or a set of alternatives.
+        return expected, recorded
+
+    try:
+        recorded_id_bits = int(recorded, 0) & SAMPLE_ID_BITS
+    except ValueError:
+        # The recorded side is a wildcard or a set of alternatives.
+        return expected, mask_bits(recorded, SAMPLE_ID_BITS)
+
+    if not asked:
+        return expected, mask_bits(recorded, SAMPLE_ID_BITS)
+
+    return (str(int(expected, 0) & ~SAMPLE_ID_BITS | SAMPLE_ID_EITHER),
+            str(int(recorded, 0) & ~SAMPLE_ID_BITS |
+                (SAMPLE_ID_EITHER if recorded_id_bits else 0)))
+
 class Fail(Exception):
     def __init__(self, test, msg):
         self.msg = msg
@@ -198,13 +243,33 @@ class Event(dict):
 
         return value
 
+    def hide_event_count_dependent_terms(self, term, expected, recorded):
+        if term == 'sample_type':
+            return hide_sample_id(expected, recorded)
+
+        return expected, recorded
+
+    def recorded_side(self, other):
+        """
+        Which one is what perf recorded? Both directions get compared, see
+        Test.compare(), so this can't be taken from the arguments' order.
+        """
+        if getattr(other, 'recorded', False):
+            return other
+
+        return self if getattr(self, 'recorded', False) else None
+
     def equal(self, other):
         for t in Event.terms:
             log.debug("      [%s] %s %s", t, self[t], other[t])
             if t not in self or t not in other:
                 return False
-            exp = self.hide_core_pmu_type(t, self[t], other)
-            got = self.hide_core_pmu_type(t, other[t], other)
+            recorded = self.recorded_side(other)
+            expected = other if recorded is self else self
+            exp = self.hide_core_pmu_type(t, expected[t], recorded or expected)
+            got = self.hide_core_pmu_type(t, (recorded or self)[t], expected)
+            if recorded is not None:
+                exp, got = self.hide_event_count_dependent_terms(t, exp, got)
             if not data_equal(exp, got):
                 return False
         return True
@@ -218,10 +283,15 @@ class Event(dict):
         for t in Event.terms:
             if t not in self or t not in other:
                 continue
-            exp = self.hide_core_pmu_type(t, self[t], other)
-            got = self.hide_core_pmu_type(t, other[t], other)
+            recorded = self.recorded_side(other)
+            expected = other if recorded is self else self
+            exp = self.hide_core_pmu_type(t, expected[t], recorded or expected)
+            got = self.hide_core_pmu_type(t, (recorded or self)[t], expected)
+            if recorded is not None:
+                exp, got = self.hide_event_count_dependent_terms(t, exp, got)
             if not data_equal(exp, got):
-                log.warning("expected %s=%s, got %s", t, self[t], other[t])
+                log.warning("expected %s=%s, got %s", t, expected[t],
+                            (recorded or self)[t])
 
 def parse_version(version):
     if not version:
@@ -506,6 +576,8 @@ class Test(object):
             log.debug("  loading result events")
             for f in glob.glob(tempdir + '/event*'):
                 self.load_events(f, self.result)
+            for event in self.result.values():
+                event.recorded = True
 
             # resolve group_fd to event names
             self.resolve_groups(self.expect)
-- 
2.55.0


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

* [PATCH v3 11/19] perf test attr: Ignore the dummy tracking event and the terms it carries
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (9 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 10/19] perf test attr: Accept either sample id bit perf asks for Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:25   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 12/19] perf test attr: Leave out the events of the other core PMUs Arnaldo Carvalho de Melo
                   ` (7 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

With more than one core PMU the requested events can't cover all the
CPUs, each cluster has its own PMU, so perf opens the software dummy
event to carry the tracking terms (mmap, comm, task, mmap_data), leaving
the requested events without them:

  expected mmap=1, got 0
  expected comm=1, got 0
  expected task=1, got 0

That dummy event is not part of what the expectations are about, so
leave it out of the comparison, unless the expectations ask for one,
like system-wide-dummy and test-record-dummy-C0 do.

And when perf used it to carry the tracking terms, those terms can't be
matched on the requested events, leave them out of the match as well.

The terms are left out only where the dropped dummy actually carried them,
from the sample_type it was opened with, a dummy that didn't is no reason
to stop verifying them.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/lib/attr.py | 61 ++++++++++++++++++++++++++++++
 1 file changed, 61 insertions(+)

diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
index da4614068772eaec..c39f2603ec077b2c 100644
--- a/tools/perf/tests/shell/lib/attr.py
+++ b/tools/perf/tests/shell/lib/attr.py
@@ -77,6 +77,21 @@ PERF_TYPE_HARDWARE = 0
 PERF_TYPE_HW_CACHE = 3
 PERF_SAMPLE_ID = 1 << 6
 PERF_SAMPLE_IDENTIFIER = 1 << 16
+PERF_TYPE_SOFTWARE = 1
+PERF_COUNT_SW_DUMMY = 9
+
+def is_dummy_tracking_event(event):
+    """
+    Is this the dummy event perf opens to carry the tracking events (mmap,
+    comm, task) when the requested ones can't, like when there is more than one
+    core PMU and each core PMU's events run on only part of the CPUs?
+    """
+    return (event.get('type') == str(PERF_TYPE_SOFTWARE) and
+            event.get('config') == str(PERF_COUNT_SW_DUMMY))
+
+# With more than one core PMU the tracking terms go to the dummy tracking event,
+# leaving the requested events without them, so they can't be matched there.
+TRACKING_TERMS = ['mmap', 'comm', 'task', 'mmap_data']
 
 # The legacy event types, where perf may qualify 'config' with the core PMU
 # type in its top 32 bits when there is more than one core PMU.
@@ -259,11 +274,29 @@ class Event(dict):
 
         return self if getattr(self, 'recorded', False) else None
 
+    def hide_tracking_via_dummy(self, term, other):
+        """
+        The tracking terms are set on the tracking event, which is the dummy
+        event when there is more than one core PMU, leaving the requested
+        events without them, nothing to match on then.
+        """
+        if term not in TRACKING_TERMS:
+            return False
+
+        for event in (self, other):
+            if term in getattr(event, 'tracking_moved_to_dummy', ()):
+                return True
+
+        return False
+
     def equal(self, other):
         for t in Event.terms:
             log.debug("      [%s] %s %s", t, self[t], other[t])
             if t not in self or t not in other:
                 return False
+            if self.hide_tracking_via_dummy(t, other):
+                continue
+
             recorded = self.recorded_side(other)
             expected = other if recorded is self else self
             exp = self.hide_core_pmu_type(t, expected[t], recorded or expected)
@@ -283,6 +316,9 @@ class Event(dict):
         for t in Event.terms:
             if t not in self or t not in other:
                 continue
+            if self.hide_tracking_via_dummy(t, other):
+                continue
+
             recorded = self.recorded_side(other)
             expected = other if recorded is self else self
             exp = self.hide_core_pmu_type(t, expected[t], recorded or expected)
@@ -498,6 +534,29 @@ class Test(object):
             else:
                 raise Unsup(self)
 
+    def drop_dummy_tracking_events(self):
+        """
+        Leave out the dummy event perf itself opened, see
+        is_dummy_tracking_event(), unless the expectations ask for one.
+        """
+        if any(is_dummy_tracking_event(e) for e in self.expect.values()):
+            return
+
+        if not any(is_dummy_tracking_event(e) for e in self.result.values()):
+            return
+
+        moved = set()
+        for name, event in list(self.result.items()):
+            if is_dummy_tracking_event(event):
+                log.debug("      dropping dummy tracking event [%s]", name)
+                moved.update(t for t in TRACKING_TERMS if event.get(t) == '1')
+                del self.result[name]
+
+        # The tracking terms the dropped dummies carried, flag the remaining
+        # events so that the match leaves only those out.
+        for event in self.result.values():
+            event.tracking_moved_to_dummy = moved
+
     def compare(self, expect, result, strict_groups=True):
         match = {}
 
@@ -579,6 +638,8 @@ class Test(object):
             for event in self.result.values():
                 event.recorded = True
 
+            self.drop_dummy_tracking_events()
+
             # resolve group_fd to event names
             self.resolve_groups(self.expect)
             self.resolve_groups(self.result)
-- 
2.55.0


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

* [PATCH v3 12/19] perf test attr: Leave out the events of the other core PMUs
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (10 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 11/19] perf test attr: Ignore the dummy tracking event and the terms it carries Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:24   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 13/19] perf test attr: Ignore the group read_format bit the PMU count can remove Arnaldo Carvalho de Melo
                   ` (6 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

On a hybrid machine the default stat metrics are opened on each core PMU,
and the events of the other one have their own encodings: on an i7-14700K
cpu_atom (type 10) opens UOPS_RETIRED.ALL, config 194, which the
expectations, written for cpu_core (type 4), don't name:

  expected type=4, got 10
  expected config=209|391, got 194

The test already masks the core PMU type out of 'config' for the legacy
events, but raw events keep their type and encoding, so leave out the
events of the core PMUs no expectation names, like the dummy tracking
event.  Core PMUs are found as perf finds them: 'cpu' or 'cpum_cf', or a
PMU with a 'cpus' file in sysfs.  Nothing is left out unless an
expectation names a core PMU, so single core PMU machines and the tests
with only legacy expectations are not affected.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/lib/attr.py | 39 ++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
index c39f2603ec077b2c..260e3ec077932cab 100644
--- a/tools/perf/tests/shell/lib/attr.py
+++ b/tools/perf/tests/shell/lib/attr.py
@@ -557,6 +557,44 @@ class Test(object):
         for event in self.result.values():
             event.tracking_moved_to_dummy = moved
 
+    def drop_other_core_pmu_events(self, devices='/sys/bus/event_source/devices'):
+        """
+        Leave out the events of the other core PMUs, which have their own
+        encodings, when the expectations name one of the core PMUs.
+        """
+        asked = set()
+        for event in self.expect.values():
+            types = event.get('type', '').split('|')
+            if '*' in types:
+                return
+            asked.update(types)
+
+        try:
+            names = os.listdir(devices)
+        except OSError:
+            return
+
+        core = set()
+        for name in names:
+            path = os.path.join(devices, name)
+            has_cpus = os.path.exists(os.path.join(path, 'cpus'))
+            if name not in ('cpu', 'cpum_cf') and not has_cpus:
+                continue
+            try:
+                with open(os.path.join(path, 'type'), encoding='utf-8') as f:
+                    core.add(f.read().strip())
+            except OSError:
+                pass
+
+        if not core & asked:
+            return
+
+        others = core - asked
+        for name, event in list(self.result.items()):
+            if event.get('type') in others:
+                log.debug("      dropping other core PMU event [%s]", name)
+                del self.result[name]
+
     def compare(self, expect, result, strict_groups=True):
         match = {}
 
@@ -639,6 +677,7 @@ class Test(object):
                 event.recorded = True
 
             self.drop_dummy_tracking_events()
+            self.drop_other_core_pmu_events()
 
             # resolve group_fd to event names
             self.resolve_groups(self.expect)
-- 
2.55.0


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

* [PATCH v3 13/19] perf test attr: Ignore the group read_format bit the PMU count can remove
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (11 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 12/19] perf test attr: Leave out the events of the other core PMUs Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:24   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 14/19] perf test attr: State, spelled out in a line, why a test is skipped Arnaldo Carvalho de Melo
                   ` (5 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

read_format's PERF_FORMAT_GROUP bit asks for the group members to be read
along with the leader, which aside from this bit is verified with the
fd/group_fd based group checking in Test.compare(), and with more than
one core PMU a group whose members are pinned to one of them, say
'default_core', leaves the clones for the other core PMUs without any
members:

  $ perf stat -e '{cycles,default_core/legacy-hardware-config=3/}:S'
  ... the A53 leader keeps its cohort, both A73 cycles clones are alone,
      read_format 20 instead of 28

so that bit isn't there: whether it is there is a consequence of how the
evlist got expanded to more than one core PMU, not of the groupness the
expectations say about, which is verified structurally, so take it out on
both sides.

That leaves the A73 leader clones, now with:

  expected read_format=28|31, got 20

matching the base event expectations again.

With a single core PMU there is nothing to expand, so there is nothing to
explain away: count the core PMUs when the script starts, from what perf
makes of 'cycles', see parse_events_multi_pmu_add(), and take the bit out
only when there is more than one, leaving the machines with a single core
PMU verifying what the expectations say about it.  That count is 2 on the
Amlogic A311D, where its A53 and A73 PMUs both answer for 'cycles', and 1
on a machine with a single core PMU.  Where there is no binding to ask,
as when attr.py runs without setup_python.sh, the count isn't available
and the bit is taken out as well.

core_pmu_count() takes the count from the same probe core_pmu_available()
uses, so the binding is asked once, and only when a comparison needs the
answer, not at import time.

The sample id bits hide_sample_id() takes out when the expectations ask
for none are another consequence of having more than one core PMU, so the
same single core PMU gate covers them; equating the two id bits, needed
when the evlist has more than one event even with a single core PMU, keeps
applying.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/lib/attr.py | 75 +++++++++++++++++++++++++-----
 1 file changed, 63 insertions(+), 12 deletions(-)

diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
index 260e3ec077932cab..ef1e29f96cc418e4 100644
--- a/tools/perf/tests/shell/lib/attr.py
+++ b/tools/perf/tests/shell/lib/attr.py
@@ -30,6 +30,7 @@ def data_equal(a, b):
     return False
 
 CORE_PMU_AVAILABLE = None
+CORE_PMU_COUNT = None
 
 def core_pmu_available():
     """
@@ -38,40 +39,47 @@ def core_pmu_available():
     check the expectations that pin their legacy encodings.  The answer can't
     change while the tests run, so probe the binding only once.
     """
-    global CORE_PMU_AVAILABLE
-
-    if CORE_PMU_AVAILABLE is None:
-        CORE_PMU_AVAILABLE = core_pmu_probe()
-
+    core_pmu_probe()
     return CORE_PMU_AVAILABLE
 
 def core_pmu_probe():
     """
-    Ask perf itself, through its python binding, to parse and open 'cycles':
-    a parse failure or an ENOENT means no PMU can count it, any other error
-    means we can't tell whether there is one.
+    Ask perf itself, through its python binding, to parse and open 'cycles'
+    once: a parse failure or an ENOENT means no PMU can count it, any other
+    error means we can't tell whether there is one, and the number of events
+    it expanded to is the number of core PMUs, see
+    parse_events_multi_pmu_add(), which is what core_pmu_count() asks for.
     """
+    global CORE_PMU_AVAILABLE, CORE_PMU_COUNT
+
+    if CORE_PMU_AVAILABLE is not None:
+        return
+
     try:
         import perf
         parse_events = perf.parse_events
     except (ImportError, AttributeError):
         # Nothing to ask about it, let the expectations speak.
-        return True
+        CORE_PMU_AVAILABLE = True
+        return
 
     try:
         evlist = parse_events('cycles')
     except OSError:
         # No PMU that can count it, the same as the ENOENT below.
-        return False
+        CORE_PMU_AVAILABLE = False
+        return
 
+    CORE_PMU_COUNT = len(evlist)
     try:
         evlist.open()
     except OSError as err:
         # Anything else is a permission problem or alike, not our business here.
-        return err.errno != errno.ENOENT
+        CORE_PMU_AVAILABLE = err.errno != errno.ENOENT
+        return
     evlist.close()
 
-    return True
+    CORE_PMU_AVAILABLE = True
 
 PERF_TYPE_HARDWARE = 0
 PERF_TYPE_HW_CACHE = 3
@@ -131,6 +139,45 @@ SAMPLE_ID_BITS = PERF_SAMPLE_ID | PERF_SAMPLE_IDENTIFIER
 # sample id bits" when comparing the two sides.
 SAMPLE_ID_EITHER = 1 << 40
 
+PERF_FORMAT_GROUP = 1 << 3
+
+def core_pmu_count():
+    """
+    How many core PMUs does perf see?  An event with no PMU named in it
+    becomes one event per core PMU, see parse_events_multi_pmu_add(), and
+    that is the expansion that leaves the clones for the other core PMUs
+    without any group members and that makes perf qualify 'config' with the
+    type of the PMU it is for, so this is the number of events perf makes of
+    'cycles', from the probe core_pmu_available() shares.
+    """
+    core_pmu_probe()
+    return CORE_PMU_COUNT
+
+def single_core_pmu():
+    """
+    Does perf see only one core PMU?  Asked for by the comparisons that need
+    it, not at import time, as a run may never get to one.
+    """
+    return core_pmu_count() == 1
+
+def hide_group_format(expected, recorded):
+    """
+    read_format's PERF_FORMAT_GROUP bit asks for the group members to be read
+    along with the leader, which aside from this bit is verified with the
+    fd/group_fd based group checking, and with more than one core PMU a group
+    whose members are pinned to one of them, say 'default_core', leaves the
+    clones for the other core PMUs without any members, so that bit isn't
+    there: whether it is there is a consequence of how the evlist got
+    expanded to more than one core PMU, not of what the expectations are
+    about, so take it out on both sides.  On a machine with a single core PMU
+    there is nothing to expand, so the expectations keep verifying it.
+    """
+    if single_core_pmu():
+        return expected, recorded
+
+    return (mask_bits(expected, PERF_FORMAT_GROUP),
+            mask_bits(recorded, PERF_FORMAT_GROUP))
+
 def hide_sample_id(expected, recorded):
     """
     perf asks for a way to tell the samples apart, PERF_SAMPLE_IDENTIFIER where
@@ -160,6 +207,8 @@ def hide_sample_id(expected, recorded):
         return expected, mask_bits(recorded, SAMPLE_ID_BITS)
 
     if not asked:
+        if single_core_pmu():
+            return expected, recorded
         return expected, mask_bits(recorded, SAMPLE_ID_BITS)
 
     return (str(int(expected, 0) & ~SAMPLE_ID_BITS | SAMPLE_ID_EITHER),
@@ -261,6 +310,8 @@ class Event(dict):
     def hide_event_count_dependent_terms(self, term, expected, recorded):
         if term == 'sample_type':
             return hide_sample_id(expected, recorded)
+        if term == 'read_format':
+            return hide_group_format(expected, recorded)
 
         return expected, recorded
 
-- 
2.55.0


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

* [PATCH v3 14/19] perf test attr: State, spelled out in a line, why a test is skipped
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (12 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 13/19] perf test attr: Ignore the group read_format bit the PMU count can remove Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:24   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 15/19] perf tests: Introduce a 'needs_mlock' suite flag Arnaldo Carvalho de Melo
                   ` (4 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

Per skip the default output shows a self contained one liner:

  unsupp  'test-record-branch-filter-any' exit 255, expected 1, no attrs recorded to verify
  skipped [kernel newer than 6.12] 'test-record-group-sampling'
  skipped [arch = !aarch64, machine = aarch64] 'test-record-graph-default'

The extra notes explaining the reason, with a copy pasteable command to
observe the failure, come in the very same stream when the verbosity is
bumped, at -vvv, nothing is written to a separate place for later
consultation, it is just the logging ladder:

  detail: '/root/bin/perf record -o /dev/null --no-bpf-event -j any kill'
  exited with 255, the expectations want the workload's 1 exit, so there
  are no attrs recorded to verify; this is normally a capability problem,
  run '/root/bin/perf record -o /dev/null --no-bpf-event -j any kill' to
  see why

Note that 'perf test -v/-vv/-vvv attribute' appends a single -v to the
shell test entry (shell_test__run()), so to see the notes one runs the
python module directly, f.i.:

  python3 tests/shell/lib/attr.py -vvv -d tests/shell/attr -p ./perf

The skip functions now return the reason fragment when they want to skip
and None when the test can go on.

The command suggested to observe the failure writes to /dev/null, so it
doesn't leave a perf.data in the directory it is run from.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/lib/attr.py | 106 +++++++++++++++++++----------
 1 file changed, 70 insertions(+), 36 deletions(-)

diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
index ef1e29f96cc418e4..a42e890ed73efc90 100644
--- a/tools/perf/tests/shell/lib/attr.py
+++ b/tools/perf/tests/shell/lib/attr.py
@@ -223,17 +223,20 @@ class Fail(Exception):
         return '\'%s\' - %s' % (self.test.path, self.msg)
 
 class Notest(Exception):
-    def __init__(self, test, arch):
-        self.arch = arch
+    def __init__(self, test, reason):
+        self.reason = reason
         self.test = test
     def getMsg(self):
-        return '[%s] \'%s\'' % (self.arch, self.test.path)
+        return '[%s] \'%s\'' % (self.reason, self.test.path)
 
 class Unsup(Exception):
-    def __init__(self, test):
+    def __init__(self, test, ret, expected):
         self.test = test
+        self.ret = ret
+        self.expected = expected
     def getMsg(self):
-        return '\'%s\'' % self.test.path
+        return '\'%s\' exit %d, expected %s, no attrs recorded to verify' % (
+            self.test.path, self.ret, self.expected)
 
 class Event(dict):
     terms = [
@@ -385,6 +388,9 @@ def parse_version(version):
         return None
     return [int(v) for v in version.split(".")[0:2]]
 
+def format_version(version):
+    return ".".join(str(v) for v in version)
+
 # Test file description needs to have following sections:
 # [config]
 #   - just single instance in file
@@ -450,13 +456,17 @@ class Test(object):
 
     def skip_test_kernel_since(self):
         if not self.kernel_since:
-            return False
-        return not self.kernel_since <= parse_version(platform.release())
+            return None
+        if self.kernel_since <= parse_version(platform.release()):
+            return None
+        return "kernel older than %s" % format_version(self.kernel_since)
 
     def skip_test_kernel_until(self):
         if not self.kernel_until:
-            return False
-        return not parse_version(platform.release()) < self.kernel_until
+            return None
+        if parse_version(platform.release()) < self.kernel_until:
+            return None
+        return "kernel newer than %s" % format_version(self.kernel_until)
 
     def skip_test_auxv(self):
         def new_auxv(a, pattern):
@@ -472,18 +482,20 @@ class Test(object):
             return (items[0], value)
 
         if not self.auxv:
-            return False
+            return None
         auxv = subprocess.check_output("LD_SHOW_AUXV=1 sleep 0", shell=True) \
                .decode(sys.stdout.encoding)
         pattern = re.compile(r"[: ]+")
         auxv = dict([new_auxv(a, pattern) for a in auxv.splitlines()])
-        return not eval(self.auxv)
+        if eval(self.auxv):
+            return None
+
+        return "the auxv condition doesn't hold here"
 
     def skip_test_arch(self, myarch):
         # If architecture not set always run test
         if self.arch == '':
-            # log.warning("test for arch %s is ok" % myarch)
-            return False
+            return None
 
         # Allow multiple values in assignment separated by ','
         arch_list = self.arch.split(',')
@@ -493,27 +505,26 @@ class Test(object):
             arch_list[0] = arch_list[0][1:]
             log.warning("excluded architecture list %s", arch_list)
             for arch_item in arch_list:
-                # log.warning("test for %s arch is %s" % (arch_item, myarch))
                 if arch_item == myarch:
-                    return True
-            return False
+                    return "arch = %s, machine = %s" % (self.arch, myarch)
+            return None
 
         for arch_item in arch_list:
-            # log.warning("test for architecture '%s' current '%s'" % (arch_item, myarch))
             if arch_item == myarch:
-                return False
-        return True
+                return None
+
+        return "arch = %s, machine = %s" % (self.arch, myarch)
 
     def skip_test_requires(self):
         for requirement in filter(None, map(str.strip, self.requires.split(','))):
             if requirement == 'core-pmu':
                 if not core_pmu_available():
                     log.warning("test limitation 'requires %s'", requirement)
-                    return True
+                    return "needs a core PMU, none here"
             else:
                 log.warning("unknown requirement '%s', ignored", requirement)
 
-        return False
+        return None
 
     def restore_sample_rate(self, value=10000):
         try:
@@ -557,20 +568,36 @@ class Test(object):
     def run_cmd(self, tempdir):
         _junk1, _junk2, _junk3, _junk4, myarch = (os.uname())
 
-        if self.skip_test_arch(myarch):
-            raise Notest(self, myarch)
-
-        if self.skip_test_auxv():
-            raise Notest(self, "auxv skip")
-
-        if self.skip_test_kernel_since():
-            raise Notest(self, "old kernel skip")
-
-        if self.skip_test_kernel_until():
-            raise Notest(self, "new kernel skip")
-
-        if self.skip_test_requires():
-            raise Notest(self, "requirements skip")
+        arch = self.skip_test_arch(myarch)
+        if arch:
+            log.debug("  detail: the test file has arch = %s, the machine reports %s",
+                     self.arch, myarch)
+            raise Notest(self, arch)
+
+        auxv = self.skip_test_auxv()
+        if auxv:
+            log.debug("  detail: the auxv condition %s doesn't hold, the bits come "
+                     "from the LD_SHOW_AUXV=1 output", self.auxv)
+            raise Notest(self, auxv)
+
+        kernel = self.skip_test_kernel_since()
+        if kernel:
+            log.debug("  detail: the test file's kernel_since = %s, this kernel is %s",
+                     format_version(self.kernel_since), platform.release())
+            raise Notest(self, kernel)
+
+        kernel = self.skip_test_kernel_until()
+        if kernel:
+            log.debug("  detail: the test file's kernel_until = %s, this kernel is %s",
+                     format_version(self.kernel_until), platform.release())
+            raise Notest(self, kernel)
+
+        requirement = self.skip_test_requires()
+        if requirement:
+            log.debug("  detail: requires core-pmu, nothing countable here, check "
+                     "kernel.perf_event_paranoid and the PMU nodes under "
+                     "/sys/bus/event_source/")
+            raise Notest(self, requirement)
 
         self.restore_sample_rate()
         cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir,
@@ -580,10 +607,17 @@ class Test(object):
         log.info("  '%s' ret '%s', expected '%s'", cmd, str(ret), str(self.ret))
 
         if not data_equal(str(ret), str(self.ret)):
+            plain_cmd = "%s %s -o /dev/null %s" % (
+                self.perf, self.command,
+                self.args.split('>/dev/null')[0].rstrip())
             if self.test_ret:
                 raise Fail(self, "Perf exit code failure")
             else:
-                raise Unsup(self)
+                log.debug("  detail: '%s' exited with %d, the expectations want the "
+                         "workload's %s exit, so there are no attrs recorded to "
+                         "verify; this is normally a capability problem, run '%s' "
+                         "to see why", plain_cmd, ret, self.ret, plain_cmd)
+                raise Unsup(self, ret, str(self.ret))
 
     def drop_dummy_tracking_events(self):
         """
-- 
2.55.0


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

* [PATCH v3 15/19] perf tests: Introduce a 'needs_mlock' suite flag
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (13 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 14/19] perf test attr: State, spelled out in a line, why a test is skipped Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:27   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 16/19] perf tests: Mark the C tests mapping perf ring buffers Arnaldo Carvalho de Melo
                   ` (3 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

Each perf instance maps one ring buffer per CPU, all charged to the same
per user budget set in kernel.perf_event_mlock_kb, so suites that map
them need to be told apart from the ones that don't, to later have them
take turns using that budget while the other tests keep running in
parallel.

Add the flag to 'struct test_suite' and a DEFINE_SUITE_NEEDS_MLOCK()
variant for the suites using the DEFINE_SUITE() form.

Shell tests run perf, that maps ring buffers, so they are taken to need
room in that budget by default; the requisites, a comma separated list in
the parens ending their description line, name what a test needs and take
a "no_" prefix to waive it, e.g.:

  # perf stat tests (no_mlock)

"mlock" is room in that budget, so stating it only spells the default
out, "no_mlock" waives it, and "exclusive" is the catch-all that keeps a
test from running alongside any other, so it runs alone, in sequence, and
implies every other requisite, which is why it can't be waived either.
More requisites are to come, each stating what a test needs, till the
catch-all isn't needed anymore.

'perf test requisites' lists what each test needs, reusing the listing
routine and taking the same test name fragments and numbers as 'list'.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/Documentation/perf-test.txt | 35 ++++++++++-
 tools/perf/tests/builtin-test.c        | 44 ++++++++++++--
 tools/perf/tests/tests-scripts.c       | 84 ++++++++++++++++++++++++--
 tools/perf/tests/tests.h               | 17 ++++++
 4 files changed, 166 insertions(+), 14 deletions(-)

diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt
index 859df74e62efc4b1..156c6e8caa75f153 100644
--- a/tools/perf/Documentation/perf-test.txt
+++ b/tools/perf/Documentation/perf-test.txt
@@ -8,7 +8,7 @@ perf-test - Runs sanity tests.
 SYNOPSIS
 --------
 [verse]
-'perf test [<options>] [{list <test-name-fragment>|[<test-name-fragments>|<test-numbers>]}]'
+'perf test [<options>] [{list|requisites} [<test-name-fragment>]|[<test-name-fragments>|<test-numbers>]]'
 
 DESCRIPTION
 -----------
@@ -18,9 +18,38 @@ also will look for a directory with more tests in the form of scripts.
 To get a list of available tests use 'perf test list', specifying a test name
 fragment will show all tests that have it.
 
+To get a list of what the tests need, i.e. of their requisites (see below), use
+'perf test requisites', that takes the same test name fragments and numbers as
+'list'.
+
 To run just specific tests, inform test name fragments or the numbers obtained
 from 'perf test list'.
 
+REQUISITES
+----------
+Shell tests run perf, that maps ring buffers, so they are taken to need room
+in the per user budget set in kernel.perf_event_mlock_kb, shared by the tests
+running at the same time.  Tests state what they need, or waive, in a comma
+separated list in the parens ending their description line, e.g.:
+
+	# perf stat tests (no_mlock)
+
+Each requisite is named in that list, "mlock" being room in the budget set in
+kernel.perf_event_mlock_kb, and takes a "no_" prefix to waive it: "(mlock)"
+states the default, "(no_mlock)" waives it, for tests that don't map ring
+buffers.  Leaving a requisite out states nothing about it, so what happens
+then is up to each kind of test: shell ones run perf, that maps ring buffers,
+so they are taken to need room in the budget unless they waive it, while C
+suites say which ones map, see DEFINE_SUITE_NEEDS_MLOCK() in tests.h.
+"exclusive" is a catch-all: the test can't run alongside any other one, so
+it runs alone, in sequence, and implies every other requisite; it can't be
+waived.  More requisites are to come, each stating what a test needs, till
+the catch-all isn't needed anymore.
+
+Use 'perf test requisites' to list what each test needs.  That listing states
+the needs, not the requisites form used in the scripts: a test listed as
+needing "mlock" is one whose script doesn't say "(no_mlock)".
+
 OPTIONS
 -------
 -s::
@@ -36,8 +65,8 @@ OPTIONS
 
 -S::
 --sequential::
-	Run all tests one after the other. By default "exclusive"
-	tests are run sequentially, but other tests are run in
+	Run all tests one after the other. By default the tests asking to be
+	"exclusive" (see REQUISITES) run sequentially, but other tests run in
 	parallel to speed execution.
 
 -r::
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index 98134b1c74cf80f4..acffa0d0d33d6d30 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -56,6 +56,8 @@ static struct strbuf junit_xml_buf = STRBUF_INIT;
 static bool dont_fork;
 /* Fork the tests in parallel and wait for their completion. */
 static bool sequential;
+/* List what each test needs, set by the "requisites" subcommand. */
+static bool list_requisites;
 /* Number of times each test is run. */
 static unsigned int runs_per_test = 1;
 /* Number of lines to include in failure snippet. */
@@ -283,6 +285,30 @@ static const char *test_description(const struct test_suite *t, int test_case)
 	return t->desc;
 }
 
+static void requisites__fprintf(FILE *fp, const struct test_suite *t, int test_case)
+{
+	bool needs_mlock = t->needs_mlock, exclusive;
+
+	if (test_case < 0)
+		test_case = test_suite__num_test_cases(t) == 1 ? 0 : -1;
+
+	if (test_case >= 0) {
+		exclusive = t->test_cases[test_case].exclusive;
+		/* The catch-all implies every other requisite. */
+		needs_mlock = needs_mlock || exclusive;
+	} else {
+		exclusive = false;
+	}
+
+	if (!exclusive && !needs_mlock)
+		return;
+
+	fprintf(fp, " (");
+	if (exclusive)
+		fprintf(fp, "exclusive, ");
+	fprintf(fp, "mlock)");
+}
+
 static test_fnptr test_function(const struct test_suite *t, int test_case)
 {
 	if (test_case <= 0)
@@ -1450,14 +1476,20 @@ static int perf_test__list(FILE *fp, struct test_suite **suites, int argc, const
 		if (!perf_test__matches(test_description(*t, -1), curr_suite, argc, argv))
 			continue;
 
-		fprintf(fp, "%3d: %s\n", curr_suite + 1, test_description(*t, -1));
+		fprintf(fp, "%3d: %s", curr_suite + 1, test_description(*t, -1));
+		if (list_requisites)
+			requisites__fprintf(fp, *t, /*test_case=*/-1);
+		fprintf(fp, "\n");
 
 		if (test_suite__num_test_cases(*t) <= 1)
 			continue;
 
 		test_suite__for_each_test_case(*t, curr_test_case) {
-			fprintf(fp, "%3d.%1d: %s\n", curr_suite + 1, curr_test_case + 1,
+			fprintf(fp, "%3d.%1d: %s", curr_suite + 1, curr_test_case + 1,
 				test_description(*t, curr_test_case));
+			if (list_requisites)
+				requisites__fprintf(fp, *t, curr_test_case);
+			fprintf(fp, "\n");
 		}
 	}
 	return 0;
@@ -1747,7 +1779,8 @@ static struct test_suite **build_suites(void)
 int cmd_test(int argc, const char **argv)
 {
 	const char *test_usage[] = {
-	"perf test [<options>] [{list <test-name-fragment>|[<test-name-fragments>|<test-numbers>]}]",
+	"perf test [<options>] [{list|requisites} [<test-name-fragment>]|"
+	"[<test-name-fragments>|<test-numbers>]]",
 	NULL,
 	};
 	const char *skip = NULL;
@@ -1776,7 +1809,7 @@ int cmd_test(int argc, const char **argv)
 			  "Generate junit XML output, default test.xml", "test.xml"),
 	OPT_END()
 	};
-	const char * const test_subcommands[] = { "list", NULL };
+	const char * const test_subcommands[] = { "list", "requisites", NULL };
 	struct intlist *skiplist = NULL;
 	struct test_suite **suites;
 	int ret;
@@ -1808,10 +1841,11 @@ int cmd_test(int argc, const char **argv)
 	setvbuf(stdout, NULL, _IONBF, 0);
 
 	argc = parse_options_subcommand(argc, argv, test_options, test_subcommands, test_usage, 0);
-	if (argc >= 1 && !strcmp(argv[0], "list")) {
+	if (argc >= 1 && (!strcmp(argv[0], "list") || !strcmp(argv[0], "requisites"))) {
 		suites = build_suites();
 		if (!suites)
 			return errno ? -errno : -ENOMEM;
+		list_requisites = !strcmp(argv[0], "requisites");
 		ret = perf_test__list(stdout, suites, argc - 1, argv + 1);
 		free(suites);
 		return ret;
diff --git a/tools/perf/tests/tests-scripts.c b/tools/perf/tests/tests-scripts.c
index 0370bc7013737e31..0bc84372eae09abe 100644
--- a/tools/perf/tests/tests-scripts.c
+++ b/tools/perf/tests/tests-scripts.c
@@ -179,6 +179,79 @@ static int shell_test__run(struct test_suite *test, int subtest __maybe_unused)
 	return WEXITSTATUS(err) == 2 ? TEST_SKIP : TEST_FAIL;
 }
 
+/*
+ * The requisites are a comma separated list in the parens ending the
+ * description line, e.g. "perf stat tests (no_mlock)": "exclusive" is the
+ * catch-all, implying every other requisite, "no_mlock" waives the default of
+ * needing room in the per user budget set in kernel.perf_event_mlock_kb.
+ */
+static void shell_test__requisites(char *desc, bool *exclusive, bool *needs_mlock)
+{
+	char *lparen, *rparen, *requisites, *token, *tokp;
+	bool known = false;
+
+	/* Only a list closing the description line is taken for requisites. */
+	rparen = strrchr(desc, ')');
+	if (rparen == NULL || rparen[1] != '\0')
+		return;
+
+	lparen = strrchr(desc, '(');
+	if (lparen == NULL || lparen > rparen)
+		return;
+
+	/* Tokenize a copy: a list with nothing recognized leaves desc alone. */
+	requisites = strndup(lparen + 1, rparen - lparen - 1);
+	if (requisites == NULL) {
+		pr_err("Failed to copy the requisites of '%s'\n", desc);
+		return;
+	}
+
+	for (token = strtok_r(requisites, ",", &tokp); token;
+	     token = strtok_r(NULL, ",", &tokp)) {
+		char *end = token + strlen(token);
+		const char *requisite = token;
+		bool waived;
+
+		while (isspace(*token))
+			token++;
+		while (end > token && isspace(end[-1]))
+			end--;
+		*end = '\0';
+
+		/* A "no_" prefix waives the requisite named after it. */
+		waived = strncmp(token, "no_", sizeof("no_") - 1) == 0;
+		if (waived)
+			token += sizeof("no_") - 1;
+
+		if (!strcmp(token, "mlock")) {
+			*needs_mlock = !waived;
+		} else if (!strcmp(token, "exclusive") && !waived) {
+			*exclusive = true;
+		} else {
+			pr_debug("Ignoring %s requisite '%s' in '%s'\n",
+				 strcmp(token, "exclusive") == 0 ? "a waived" : "an unknown",
+				 requisite, desc);
+			continue;
+		}
+
+		known = true;
+	}
+
+	zfree(&requisites);
+
+	if (!known)
+		return;
+
+	/* Drop the list and the space before it from the description. */
+	*lparen = '\0';
+	if (lparen > desc && isspace(lparen[-1]))
+		lparen[-1] = '\0';
+
+	/* The catch-all implies every other requisite, whatever the order. */
+	if (*exclusive)
+		*needs_mlock = true;
+}
+
 static void append_script(int dir_fd, const char *name, char *desc,
 			  struct test_suite ***result,
 			  size_t *result_sz)
@@ -187,7 +260,8 @@ static void append_script(int dir_fd, const char *name, char *desc,
 	struct test_suite *test_suite, **result_tmp;
 	struct test_case *tests;
 	ssize_t len;
-	char *exclusive;
+	/* Shell tests run perf, that maps ring buffers, so they need the budget. */
+	bool exclusive = false, needs_mlock = true;
 
 	snprintf(link, sizeof(link), "/proc/%d/fd/%d", getpid(), dir_fd);
 	len = readlink(link, filename, sizeof(filename) - 1);
@@ -204,11 +278,8 @@ static void append_script(int dir_fd, const char *name, char *desc,
 		return;
 	}
 	tests[0].name = strdup_check(name);
-	exclusive = strstr(desc, " (exclusive)");
-	if (exclusive != NULL) {
-		tests[0].exclusive = true;
-		exclusive[0] = '\0';
-	}
+	shell_test__requisites(desc, &exclusive, &needs_mlock);
+	tests[0].exclusive = exclusive;
 	tests[0].desc = strdup_check(desc);
 	tests[0].run_case = shell_test__run;
 	test_suite = zalloc(sizeof(*test_suite));
@@ -220,6 +291,7 @@ static void append_script(int dir_fd, const char *name, char *desc,
 	test_suite->desc = desc;
 	test_suite->test_cases = tests;
 	test_suite->priv = strdup_check(filename);
+	test_suite->needs_mlock = needs_mlock;
 	/* Realloc is good enough, though we could realloc by chunks, not that
 	 * anyone will ever measure performance here */
 	result_tmp = realloc(*result, (*result_sz + 1) * sizeof(*result_tmp));
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
index f379620069d34f3a..5982c4e62cd37206 100644
--- a/tools/perf/tests/tests.h
+++ b/tools/perf/tests/tests.h
@@ -46,6 +46,12 @@ struct test_suite {
 	struct test_case *test_cases;
 	void *priv;
 	int (*setup)(struct test_suite *suite);
+	/*
+	 * Set for the suites mapping perf ring buffers, that need room in the
+	 * per user budget set in kernel.perf_event_mlock_kb, see
+	 * shell_test__requisites() for the shell suites.
+	 */
+	bool needs_mlock;
 };
 
 #define DECLARE_SUITE(name) \
@@ -103,6 +109,17 @@ struct test_suite {
 		.test_cases = tests__##_name,		\
 	}
 
+#define DEFINE_SUITE_NEEDS_MLOCK(description, _name)	\
+	struct test_case tests__##_name[] = {           \
+		TEST_CASE(description, _name),		\
+		{	.name = NULL, }			\
+	};						\
+	struct test_suite suite__##_name = {		\
+		.desc = description,			\
+		.test_cases = tests__##_name,		\
+		.needs_mlock = true,			\
+	}
+
 /* Tests */
 DECLARE_SUITE(vmlinux_matches_kallsyms);
 DECLARE_SUITE(openat_syscall_event);
-- 
2.55.0


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

* [PATCH v3 16/19] perf tests: Mark the C tests mapping perf ring buffers
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (14 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 15/19] perf tests: Introduce a 'needs_mlock' suite flag Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:24   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 17/19] perf tests: Mark the shell tests not " Arnaldo Carvalho de Melo
                   ` (2 subsequent siblings)
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

The C tests that run 'perf record' in the test process itself map ring
buffers and so need room in the per user budget set in
kernel.perf_event_mlock_kb: mark them with the new 'needs_mlock' suite
flag, read by the turnstile added later in the series, so that they
take turns using that budget while the tests that don't map them keep
running in parallel.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/backward-ring-buffer.c     | 2 +-
 tools/perf/tests/code-reading.c             | 2 +-
 tools/perf/tests/keep-tracking.c            | 2 +-
 tools/perf/tests/mmap-basic.c               | 1 +
 tools/perf/tests/openat-syscall-tp-fields.c | 1 +
 tools/perf/tests/perf-record.c              | 1 +
 tools/perf/tests/perf-time-to-tsc.c         | 1 +
 tools/perf/tests/sw-clock.c                 | 2 +-
 tools/perf/tests/task-exit.c                | 1 +
 9 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/tools/perf/tests/backward-ring-buffer.c b/tools/perf/tests/backward-ring-buffer.c
index 2735cc26d7eec3fb..9314696858ff7c84 100644
--- a/tools/perf/tests/backward-ring-buffer.c
+++ b/tools/perf/tests/backward-ring-buffer.c
@@ -167,4 +167,4 @@ static int test__backward_ring_buffer(struct test_suite *test __maybe_unused, in
 	return ret;
 }
 
-DEFINE_SUITE("Read backward ring buffer", backward_ring_buffer);
+DEFINE_SUITE_NEEDS_MLOCK("Read backward ring buffer", backward_ring_buffer);
diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c
index c50b526f44fb523f..130cd6021f930597 100644
--- a/tools/perf/tests/code-reading.c
+++ b/tools/perf/tests/code-reading.c
@@ -885,4 +885,4 @@ static int test__code_reading(struct test_suite *test __maybe_unused, int subtes
 	};
 }
 
-DEFINE_SUITE("Object code reading", code_reading);
+DEFINE_SUITE_NEEDS_MLOCK("Object code reading", code_reading);
diff --git a/tools/perf/tests/keep-tracking.c b/tools/perf/tests/keep-tracking.c
index b760041bed307859..6d8d8cd57200645d 100644
--- a/tools/perf/tests/keep-tracking.c
+++ b/tools/perf/tests/keep-tracking.c
@@ -161,4 +161,4 @@ static int test__keep_tracking(struct test_suite *test __maybe_unused, int subte
 	return err;
 }
 
-DEFINE_SUITE("Use a dummy software event to keep tracking", keep_tracking);
+DEFINE_SUITE_NEEDS_MLOCK("Use a dummy software event to keep tracking", keep_tracking);
diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c
index 5cec7644952c7dba..02cfe8533ac05036 100644
--- a/tools/perf/tests/mmap-basic.c
+++ b/tools/perf/tests/mmap-basic.c
@@ -448,4 +448,5 @@ static struct test_case tests__basic_mmap[] = {
 struct test_suite suite__basic_mmap = {
 	.desc = "mmap interface tests",
 	.test_cases = tests__basic_mmap,
+	.needs_mlock = true,
 };
diff --git a/tools/perf/tests/openat-syscall-tp-fields.c b/tools/perf/tests/openat-syscall-tp-fields.c
index 5365889d326f8bc7..1ea336fbc7e7c030 100644
--- a/tools/perf/tests/openat-syscall-tp-fields.c
+++ b/tools/perf/tests/openat-syscall-tp-fields.c
@@ -160,4 +160,5 @@ static struct test_case tests__syscall_openat_tp_fields[] = {
 struct test_suite suite__syscall_openat_tp_fields = {
 	.desc = "syscalls:sys_enter_openat event fields",
 	.test_cases = tests__syscall_openat_tp_fields,
+	.needs_mlock = true,
 };
diff --git a/tools/perf/tests/perf-record.c b/tools/perf/tests/perf-record.c
index 0cac6ae1a1fc05ed..370dbde08eac71c3 100644
--- a/tools/perf/tests/perf-record.c
+++ b/tools/perf/tests/perf-record.c
@@ -372,4 +372,5 @@ static struct test_case tests__PERF_RECORD[] = {
 struct test_suite suite__PERF_RECORD = {
 	.desc = "PERF_RECORD_* events & perf_sample fields",
 	.test_cases = tests__PERF_RECORD,
+	.needs_mlock = true,
 };
diff --git a/tools/perf/tests/perf-time-to-tsc.c b/tools/perf/tests/perf-time-to-tsc.c
index f8f71fdd32b1bfed..0cefa2df2db728f8 100644
--- a/tools/perf/tests/perf-time-to-tsc.c
+++ b/tools/perf/tests/perf-time-to-tsc.c
@@ -218,4 +218,5 @@ static struct test_case time_to_tsc_tests[] = {
 struct test_suite suite__perf_time_to_tsc = {
 	.desc = "Convert perf time to TSC",
 	.test_cases = time_to_tsc_tests,
+	.needs_mlock = true,
 };
diff --git a/tools/perf/tests/sw-clock.c b/tools/perf/tests/sw-clock.c
index d181858816352c67..1b62dae55f076d30 100644
--- a/tools/perf/tests/sw-clock.c
+++ b/tools/perf/tests/sw-clock.c
@@ -149,4 +149,4 @@ static int test__sw_clock_freq(struct test_suite *test __maybe_unused, int subte
 	return ret;
 }
 
-DEFINE_SUITE("Software clock events period values", sw_clock_freq);
+DEFINE_SUITE_NEEDS_MLOCK("Software clock events period values", sw_clock_freq);
diff --git a/tools/perf/tests/task-exit.c b/tools/perf/tests/task-exit.c
index 95393edbfe363e5e..9fac426ac2f9eab3 100644
--- a/tools/perf/tests/task-exit.c
+++ b/tools/perf/tests/task-exit.c
@@ -158,4 +158,5 @@ struct test_case tests__task_exit[] = {
 struct test_suite suite__task_exit = {
 	.desc = "Number of exit events of a simple workload",
 	.test_cases = tests__task_exit,
+	.needs_mlock = true,
 };
-- 
2.55.0


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

* [PATCH v3 17/19] perf tests: Mark the shell tests not mapping perf ring buffers
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (15 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 16/19] perf tests: Mark the C tests mapping perf ring buffers Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:26   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 18/19] perf mutex: Add a robust, process-shared mutex initializer Arnaldo Carvalho de Melo
  2026-10-16  0:18 ` [PATCH v3 19/19] perf test: Take turns using the perf mlock budget in the mmaping tests Arnaldo Carvalho de Melo
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

Shell tests run perf, that maps ring buffers, so they take a turn using
the per user budget in kernel.perf_event_mlock_kb by default; the ones
that don't map them, like the ones running just 'perf stat', say
" (no_mlock)" in their description line and thus keep running in parallel
with the ones taking turns.

The 'perf sched stats' tests are among them, they synthesize the samples
from /proc/schedstat without mapping a ring buffer, and the tests already
marked " (exclusive)" don't need it, exclusive implies every other
requisite and keeps them running alone.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/tests/shell/drm_pmu.sh                       | 2 +-
 tools/perf/tests/shell/ftrace.sh                        | 2 +-
 tools/perf/tests/shell/kallsyms.sh                      | 2 +-
 tools/perf/tests/shell/list.sh                          | 2 +-
 tools/perf/tests/shell/perf_sched_stats.sh              | 2 +-
 tools/perf/tests/shell/probe_vfs_getname.sh             | 2 +-
 tools/perf/tests/shell/python-use.sh                    | 2 +-
 tools/perf/tests/shell/schedstat_snapshots.sh           | 2 +-
 tools/perf/tests/shell/stat+csv_output.sh               | 2 +-
 tools/perf/tests/shell/stat+csv_summary.sh              | 2 +-
 tools/perf/tests/shell/stat+event_uniquifying.sh        | 2 +-
 tools/perf/tests/shell/stat+json_output.sh              | 2 +-
 tools/perf/tests/shell/stat+shadow_stat.sh              | 2 +-
 tools/perf/tests/shell/stat+std_output.sh               | 2 +-
 tools/perf/tests/shell/stat.sh                          | 2 +-
 tools/perf/tests/shell/stat_all_metricgroups.sh         | 2 +-
 tools/perf/tests/shell/stat_all_metrics.sh              | 2 +-
 tools/perf/tests/shell/stat_all_pfm.sh                  | 2 +-
 tools/perf/tests/shell/stat_bpf_counters_cgrp.sh        | 2 +-
 tools/perf/tests/shell/stat_metrics_cgrp.sh             | 2 +-
 tools/perf/tests/shell/stat_metrics_values.sh           | 2 +-
 tools/perf/tests/shell/test_ilist_python.sh             | 2 +-
 tools/perf/tests/shell/test_stat_cpi_python.sh          | 2 +-
 tools/perf/tests/shell/test_test_junit_output.sh        | 5 ++++-
 tools/perf/tests/shell/test_uprobe_from_different_cu.sh | 2 +-
 25 files changed, 28 insertions(+), 25 deletions(-)

diff --git a/tools/perf/tests/shell/drm_pmu.sh b/tools/perf/tests/shell/drm_pmu.sh
index e629fe0e8463d6cc..c827e613fc66ee0f 100755
--- a/tools/perf/tests/shell/drm_pmu.sh
+++ b/tools/perf/tests/shell/drm_pmu.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# DRM PMU
+# DRM PMU (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/ftrace.sh b/tools/perf/tests/shell/ftrace.sh
index 9f6e590f6437b2d9..84d6126bf94d1e61 100755
--- a/tools/perf/tests/shell/ftrace.sh
+++ b/tools/perf/tests/shell/ftrace.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf ftrace tests
+# perf ftrace tests (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/kallsyms.sh b/tools/perf/tests/shell/kallsyms.sh
index d0eb99753f477f57..e3674cc52b30983f 100755
--- a/tools/perf/tests/shell/kallsyms.sh
+++ b/tools/perf/tests/shell/kallsyms.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf kallsyms tests
+# perf kallsyms tests (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 err=0
diff --git a/tools/perf/tests/shell/list.sh b/tools/perf/tests/shell/list.sh
index 14a03e0290ed1b4d..d4a0b04154d636f0 100755
--- a/tools/perf/tests/shell/list.sh
+++ b/tools/perf/tests/shell/list.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf list tests
+# perf list tests (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/perf_sched_stats.sh b/tools/perf/tests/shell/perf_sched_stats.sh
index f13eb0a75b76ef5a..4ede9d701630c719 100755
--- a/tools/perf/tests/shell/perf_sched_stats.sh
+++ b/tools/perf/tests/shell/perf_sched_stats.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-# perf sched stats tests
+# perf sched stats tests (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/probe_vfs_getname.sh b/tools/perf/tests/shell/probe_vfs_getname.sh
index c40e5f3f0825b051..43bdd693076b408c 100755
--- a/tools/perf/tests/shell/probe_vfs_getname.sh
+++ b/tools/perf/tests/shell/probe_vfs_getname.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# Add vfs_getname probe to get syscall args filenames
+# Add vfs_getname probe to get syscall args filenames (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 # Arnaldo Carvalho de Melo <acme@kernel.org>, 2017
 
diff --git a/tools/perf/tests/shell/python-use.sh b/tools/perf/tests/shell/python-use.sh
index 2d6017d5dd9d9eab..ece4d306869ae48e 100755
--- a/tools/perf/tests/shell/python-use.sh
+++ b/tools/perf/tests/shell/python-use.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# 'import perf' in python
+# 'import perf' in python (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 # Just test if we can load the python binding.
 set -e
diff --git a/tools/perf/tests/shell/schedstat_snapshots.sh b/tools/perf/tests/shell/schedstat_snapshots.sh
index a4bdf2673d43ccc0..2050478e3f1a682f 100755
--- a/tools/perf/tests/shell/schedstat_snapshots.sh
+++ b/tools/perf/tests/shell/schedstat_snapshots.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-# Validate CPU and domain pairing in perf sched stats snapshots
+# Validate CPU and domain pairing in perf sched stats snapshots (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/stat+csv_output.sh b/tools/perf/tests/shell/stat+csv_output.sh
index a5b34c42e8d72aff..8beb6c674d055a52 100755
--- a/tools/perf/tests/shell/stat+csv_output.sh
+++ b/tools/perf/tests/shell/stat+csv_output.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf stat CSV output linter
+# perf stat CSV output linter (no_mlock)
 # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
 # Tests various perf stat CSV output commands for the
 # correct number of fields and the CSV separator set to ','.
diff --git a/tools/perf/tests/shell/stat+csv_summary.sh b/tools/perf/tests/shell/stat+csv_summary.sh
index 9a4353db3825ede9..6d8beb88fb6f300a 100755
--- a/tools/perf/tests/shell/stat+csv_summary.sh
+++ b/tools/perf/tests/shell/stat+csv_summary.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf stat csv summary test
+# perf stat csv summary test (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/stat+event_uniquifying.sh b/tools/perf/tests/shell/stat+event_uniquifying.sh
index b5dec6b6da369382..f5c6fa30d8db38d1 100755
--- a/tools/perf/tests/shell/stat+event_uniquifying.sh
+++ b/tools/perf/tests/shell/stat+event_uniquifying.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf stat events uniquifying
+# perf stat events uniquifying (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/stat+json_output.sh b/tools/perf/tests/shell/stat+json_output.sh
index 4969ad70b67b6196..cbf7a4ffd7e8ab4f 100755
--- a/tools/perf/tests/shell/stat+json_output.sh
+++ b/tools/perf/tests/shell/stat+json_output.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf stat JSON output linter
+# perf stat JSON output linter (no_mlock)
 # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
 # Checks various perf stat JSON output commands for the
 # correct number of fields.
diff --git a/tools/perf/tests/shell/stat+shadow_stat.sh b/tools/perf/tests/shell/stat+shadow_stat.sh
index cabbbf17c662df23..7a58e1507ce27957 100755
--- a/tools/perf/tests/shell/stat+shadow_stat.sh
+++ b/tools/perf/tests/shell/stat+shadow_stat.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf stat metrics (shadow stat) test
+# perf stat metrics (shadow stat) test (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/stat+std_output.sh b/tools/perf/tests/shell/stat+std_output.sh
index 64c2b81f02deb18b..b915656a20f69d68 100755
--- a/tools/perf/tests/shell/stat+std_output.sh
+++ b/tools/perf/tests/shell/stat+std_output.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf stat STD output linter
+# perf stat STD output linter (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 # Tests various perf stat STD output commands for
 # default event and metricgroup
diff --git a/tools/perf/tests/shell/stat.sh b/tools/perf/tests/shell/stat.sh
index 1f5d4cc05807a247..ac9e031831daca9c 100755
--- a/tools/perf/tests/shell/stat.sh
+++ b/tools/perf/tests/shell/stat.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf stat tests
+# perf stat tests (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/stat_all_metricgroups.sh b/tools/perf/tests/shell/stat_all_metricgroups.sh
index 81bc7070b5ab0d5c..074a437da0ae3dce 100755
--- a/tools/perf/tests/shell/stat_all_metricgroups.sh
+++ b/tools/perf/tests/shell/stat_all_metricgroups.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf all metricgroups test
+# perf all metricgroups test (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 ParanoidAndNotRoot()
diff --git a/tools/perf/tests/shell/stat_all_metrics.sh b/tools/perf/tests/shell/stat_all_metrics.sh
index df5fa27b21ae28d8..9d0d4b8a217e6730 100755
--- a/tools/perf/tests/shell/stat_all_metrics.sh
+++ b/tools/perf/tests/shell/stat_all_metrics.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf all metrics test
+# perf all metrics test (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 ParanoidAndNotRoot()
diff --git a/tools/perf/tests/shell/stat_all_pfm.sh b/tools/perf/tests/shell/stat_all_pfm.sh
index c08c186af2c42e84..78d8006fb729c8f4 100755
--- a/tools/perf/tests/shell/stat_all_pfm.sh
+++ b/tools/perf/tests/shell/stat_all_pfm.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf all libpfm4 events test
+# perf all libpfm4 events test (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 if perf version --build-options | grep HAVE_LIBPFM | grep -q OFF
diff --git a/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh b/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh
index febe3af0644157f3..d1b4632c17a3e8e1 100755
--- a/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh
+++ b/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf stat --bpf-counters --for-each-cgroup test
+# perf stat --bpf-counters --for-each-cgroup test (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/stat_metrics_cgrp.sh b/tools/perf/tests/shell/stat_metrics_cgrp.sh
index 0728f48f21fa971d..75b8e01d11cc7ff3 100755
--- a/tools/perf/tests/shell/stat_metrics_cgrp.sh
+++ b/tools/perf/tests/shell/stat_metrics_cgrp.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf stat metrics --for-each-cgroup test
+# perf stat metrics --for-each-cgroup test (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/stat_metrics_values.sh b/tools/perf/tests/shell/stat_metrics_values.sh
index 0982f6b9737a780f..3e073a0e5b9f9b99 100755
--- a/tools/perf/tests/shell/stat_metrics_values.sh
+++ b/tools/perf/tests/shell/stat_metrics_values.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf metrics value validation
+# perf metrics value validation (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 shelldir=$(dirname "$0")
diff --git a/tools/perf/tests/shell/test_ilist_python.sh b/tools/perf/tests/shell/test_ilist_python.sh
index 83048474710d2857..a235539c01767ee5 100755
--- a/tools/perf/tests/shell/test_ilist_python.sh
+++ b/tools/perf/tests/shell/test_ilist_python.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# perf script ilist and perf list --tui test
+# perf script ilist and perf list --tui test (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
diff --git a/tools/perf/tests/shell/test_stat_cpi_python.sh b/tools/perf/tests/shell/test_stat_cpi_python.sh
index 6cb376c92e2f13a7..d1571a20f4683d73 100755
--- a/tools/perf/tests/shell/test_stat_cpi_python.sh
+++ b/tools/perf/tests/shell/test_stat_cpi_python.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 # SPDX-License-Identifier: GPL-2.0
-# stat-cpi python test
+# stat-cpi python test (no_mlock)
 
 set -e
 
diff --git a/tools/perf/tests/shell/test_test_junit_output.sh b/tools/perf/tests/shell/test_test_junit_output.sh
index 38a29e646906e3aa..8fc92e083c342a5c 100755
--- a/tools/perf/tests/shell/test_test_junit_output.sh
+++ b/tools/perf/tests/shell/test_test_junit_output.sh
@@ -1,6 +1,9 @@
 #!/bin/bash
 # SPDX-License-Identifier: GPL-2.0
-# perf test junit XML output validation
+# perf test junit XML output validation (no_mlock)
+#
+# Waiving the mlock requisite this way holds only while the 'perf test'
+# runs below keep picking tests that don't map ring buffers.
 
 set -e
 
diff --git a/tools/perf/tests/shell/test_uprobe_from_different_cu.sh b/tools/perf/tests/shell/test_uprobe_from_different_cu.sh
index 33bf36c4037e45ae..0a02ff9b86d6f326 100755
--- a/tools/perf/tests/shell/test_uprobe_from_different_cu.sh
+++ b/tools/perf/tests/shell/test_uprobe_from_different_cu.sh
@@ -1,5 +1,5 @@
 #!/bin/bash
-# test perf probe of function from different CU
+# test perf probe of function from different CU (no_mlock)
 # SPDX-License-Identifier: GPL-2.0
 
 set -e
-- 
2.55.0


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

* [PATCH v3 18/19] perf mutex: Add a robust, process-shared mutex initializer
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (16 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 17/19] perf tests: Mark the shell tests not " Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:28   ` sashiko-bot
  2026-10-16  0:18 ` [PATCH v3 19/19] perf test: Take turns using the perf mlock budget in the mmaping tests Arnaldo Carvalho de Melo
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

A mutex held by a process that can die before unlocking it, like the
tests forked by 'perf test' that will take turns using the per user
budget set in kernel.perf_event_mlock_kb, has to be taken over by the
next locker instead of staying locked forever.

Robust mutexes, on top of the kernel's robust futexes, do that: when an
owner dies while holding it the kernel marks the futex word owner-died
and the next pthread_mutex_lock() returns EOWNERDEAD, then mutex_lock()
makes it consistent and takes it over; mutex_trylock() and cond_wait()
come back with EOWNERDEAD too, so they take it over the same way, with
mutex_trylock() giving the mutex back when it can't be taken over: it
is held all the same, and a failed trylock must not leak it.

Add mutex_init_pshared_robust(), returning false when robust mutexes
aren't available: from libcs without the API, gated by a feature check,
to musl and glibc refusing them at runtime, when get_robust_list(2) or
set_robust_list(2) aren't usable.  glibc only sets a flag in
pthread_mutexattr_setrobust(), refusing the mutex itself in
pthread_mutex_init() with ENOTSUP instead, other errors from it still
being reported.  Robust mutexes being unavailable isn't an error, just a
debug message for whoever then falls back.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/build/Makefile.feature                  |   1 +
 tools/build/feature/Makefile                  |   4 +
 tools/build/feature/test-all.c                |   5 +
 .../build/feature/test-pthread-mutex-robust.c |  18 ++++
 tools/perf/Makefile.config                    |   4 +
 tools/perf/util/mutex.c                       | 101 +++++++++++++++---
 tools/perf/util/mutex.h                       |   6 ++
 7 files changed, 125 insertions(+), 14 deletions(-)
 create mode 100644 tools/build/feature/test-pthread-mutex-robust.c

diff --git a/tools/build/Makefile.feature b/tools/build/Makefile.feature
index dbef5b6c4e43c857..4d37d11f674c5bd5 100644
--- a/tools/build/Makefile.feature
+++ b/tools/build/Makefile.feature
@@ -85,6 +85,7 @@ FEATURE_TESTS_BASIC :=                  \
         libcpupower                     \
         pthread-attr-setaffinity-np     \
         pthread-barrier     		\
+        pthread-mutex-robust		\
         reallocarray                    \
         stackprotector-all              \
         timerfd                         \
diff --git a/tools/build/feature/Makefile b/tools/build/feature/Makefile
index 62f9cbf3dee97936..10340a5372242e05 100644
--- a/tools/build/feature/Makefile
+++ b/tools/build/feature/Makefile
@@ -41,6 +41,7 @@ FILES=                                          \
          $(foreach arch,$(LIBUNWIND_ARCHS),test-libunwind-$(arch).bin test-libunwind-debug-frame-$(arch).bin) \
          test-pthread-attr-setaffinity-np.bin   \
          test-pthread-barrier.bin		\
+         test-pthread-mutex-robust.bin		\
          test-stackprotector-all.bin            \
          test-timerfd.bin                       \
          test-babeltrace2-ctf-writer.bin        \
@@ -128,6 +129,9 @@ $(OUTPUT)test-pthread-attr-setaffinity-np.bin:
 $(OUTPUT)test-pthread-barrier.bin:
 	$(BUILD) -lpthread
 
+$(OUTPUT)test-pthread-mutex-robust.bin:
+	$(BUILD) -lpthread
+
 $(OUTPUT)test-stackprotector-all.bin:
 	$(BUILD) -fstack-protector-all
 
diff --git a/tools/build/feature/test-all.c b/tools/build/feature/test-all.c
index 0ee16eccd9e05788..4a6890290f7462a7 100644
--- a/tools/build/feature/test-all.c
+++ b/tools/build/feature/test-all.c
@@ -90,6 +90,10 @@
 # include "test-pthread-barrier.c"
 #undef main
 
+#define main main_test_pthread_mutex_robust
+# include "test-pthread-mutex-robust.c"
+#undef main
+
 #define main main_test_scandirat
 # include "test-scandirat.c"
 #undef main
@@ -167,6 +171,7 @@ int main(int argc, char *argv[])
 	main_test_zlib();
 	main_test_pthread_attr_setaffinity_np();
 	main_test_pthread_barrier();
+	main_test_pthread_mutex_robust();
 	main_test_lzma();
 	main_test_bpf();
 	main_test_scandirat();
diff --git a/tools/build/feature/test-pthread-mutex-robust.c b/tools/build/feature/test-pthread-mutex-robust.c
new file mode 100644
index 0000000000000000..d8409074713d172b
--- /dev/null
+++ b/tools/build/feature/test-pthread-mutex-robust.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <pthread.h>
+
+int main(void)
+{
+	pthread_mutexattr_t attr;
+	pthread_mutex_t mtx;
+
+	pthread_mutexattr_init(&attr);
+	pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
+	pthread_mutex_init(&mtx, &attr);
+	pthread_mutex_consistent(&mtx);
+	pthread_mutex_destroy(&mtx);
+	pthread_mutexattr_destroy(&attr);
+
+	return 0;
+}
diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config
index cafc7be79643b26c..b4764f69f47f4d8c 100644
--- a/tools/perf/Makefile.config
+++ b/tools/perf/Makefile.config
@@ -417,6 +417,10 @@ ifeq ($(feature-pthread-barrier), 1)
   CFLAGS += -DHAVE_PTHREAD_BARRIER
 endif
 
+ifeq ($(feature-pthread-mutex-robust), 1)
+  CFLAGS += -DHAVE_PTHREAD_MUTEX_ROBUST
+endif
+
 ifndef NO_BIONIC
   $(call feature_check,bionic)
   ifeq ($(feature-bionic), 1)
diff --git a/tools/perf/util/mutex.c b/tools/perf/util/mutex.c
index 7aa1f3f55a7d4089..df3b7cce3d5a6a35 100644
--- a/tools/perf/util/mutex.c
+++ b/tools/perf/util/mutex.c
@@ -17,37 +17,106 @@ static void check_err(const char *fn, int err)
 
 #define CHECK_ERR(err) check_err(__func__, err)
 
-static void __mutex_init(struct mutex *mtx, bool pshared, bool recursive)
+#ifdef HAVE_PTHREAD_MUTEX_ROBUST
+/*
+ * A robust mutex whose owner died while holding it comes back as EOWNERDEAD,
+ * with the caller holding it: make it consistent, which takes it over; failing
+ * that, a give_up caller unlocks it, leaving it ENOTRECOVERABLE, not leaked.
+ */
+static int mutex_take_over(struct mutex *mtx, int err, bool give_up)
+{
+	if (err != EOWNERDEAD)
+		return err;
+
+	pr_debug("mutex: taking over one left by a dead owner\n");
+	err = pthread_mutex_consistent(&mtx->lock);
+	if (err && give_up)
+		CHECK_ERR(pthread_mutex_unlock(&mtx->lock));
+
+	return err;
+}
+#else
+#define mutex_take_over(mtx, err, give_up) ((void)(mtx), (void)(give_up), (err))
+#endif
+
+static bool __mutex_init(struct mutex *mtx, bool pshared, bool recursive, bool robust)
 {
 	pthread_mutexattr_t attr;
+	bool ok = false, robust_unavailable = false;
+	int err;
 
-	CHECK_ERR(pthread_mutexattr_init(&attr));
+	err = pthread_mutexattr_init(&attr);
+	if (err)
+		goto out;
 
 #ifndef NDEBUG
 	/* In normal builds enable error checking, such as recursive usage. */
-	CHECK_ERR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK));
+	err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+	if (err)
+		goto out_destroy;
 #endif
-	if (recursive)
-		CHECK_ERR(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE));
-	if (pshared)
-		CHECK_ERR(pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED));
-	CHECK_ERR(pthread_mutex_init(&mtx->lock, &attr));
+	if (recursive) {
+		err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+		if (err)
+			goto out_destroy;
+	}
+	if (pshared) {
+		err = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+		if (err)
+			goto out_destroy;
+	}
+	if (robust) {
+#ifdef HAVE_PTHREAD_MUTEX_ROBUST
+		err = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST);
+		if (err)
+			robust_unavailable = true;
+#else
+		robust_unavailable = true;
+#endif
+		if (robust_unavailable)
+			goto out_destroy;
+	}
+
+	err = pthread_mutex_init(&mtx->lock, &attr);
+	if (!err)
+		ok = true;
+	else if (robust && pshared && err == ENOTSUP)
+		/*
+		 * glibc refuses a robust process shared mutex with ENOTSUP when
+		 * set_robust_list(2) isn't usable, i.e. when the machine provides
+		 * no robust mutexes for the process shared case: an unavailable
+		 * API, not a bad request.
+		 */
+		robust_unavailable = true;
+out_destroy:
 	CHECK_ERR(pthread_mutexattr_destroy(&attr));
+out:
+	if (robust_unavailable)
+		pr_debug("mutex: robust mutexes aren't available\n");
+	else if (err)
+		CHECK_ERR(err);
+
+	return ok;
 }
 
 void mutex_init(struct mutex *mtx)
 {
-	__mutex_init(mtx, /*pshared=*/false, /*recursive=*/false);
+	__mutex_init(mtx, /*pshared=*/false, /*recursive=*/false, /*robust=*/false);
 }
 
 void mutex_init_pshared(struct mutex *mtx)
 {
-	__mutex_init(mtx, /*pshared=*/true, /*recursive=*/false);
+	__mutex_init(mtx, /*pshared=*/true, /*recursive=*/false, /*robust=*/false);
 }
 
 void mutex_init_recursive(struct mutex *mtx)
 {
-	__mutex_init(mtx, /*pshared=*/false, /*recursive=*/true);
+	__mutex_init(mtx, /*pshared=*/false, /*recursive=*/true, /*robust=*/false);
+}
+
+bool mutex_init_pshared_robust(struct mutex *mtx)
+{
+	return __mutex_init(mtx, /*pshared=*/true, /*recursive=*/false, /*robust=*/true);
 }
 
 void mutex_destroy(struct mutex *mtx)
@@ -58,7 +127,8 @@ void mutex_destroy(struct mutex *mtx)
 void mutex_lock(struct mutex *mtx)
 	NO_THREAD_SAFETY_ANALYSIS
 {
-	CHECK_ERR(pthread_mutex_lock(&mtx->lock));
+	CHECK_ERR(mutex_take_over(mtx, pthread_mutex_lock(&mtx->lock),
+				  /*give_up=*/false));
 }
 
 void mutex_unlock(struct mutex *mtx)
@@ -69,7 +139,8 @@ void mutex_unlock(struct mutex *mtx)
 
 bool mutex_trylock(struct mutex *mtx)
 {
-	int ret = pthread_mutex_trylock(&mtx->lock);
+	int ret = mutex_take_over(mtx, pthread_mutex_trylock(&mtx->lock),
+				  /*give_up=*/true);
 
 	if (ret == 0)
 		return true; /* Lock acquired. */
@@ -111,7 +182,9 @@ void cond_destroy(struct cond *cnd)
 
 void cond_wait(struct cond *cnd, struct mutex *mtx)
 {
-	CHECK_ERR(pthread_cond_wait(&cnd->cond, &mtx->lock));
+	CHECK_ERR(mutex_take_over(mtx,
+				  pthread_cond_wait(&cnd->cond, &mtx->lock),
+				  /*give_up=*/false));
 }
 
 void cond_signal(struct cond *cnd)
diff --git a/tools/perf/util/mutex.h b/tools/perf/util/mutex.h
index 5554937675886ef3..5de1065ea2b94bbb 100644
--- a/tools/perf/util/mutex.h
+++ b/tools/perf/util/mutex.h
@@ -142,6 +142,12 @@ void mutex_init(struct mutex *mtx);
 void mutex_init_pshared(struct mutex *mtx);
 /* Initializes a mutex that may be recursively held on the same thread. */
 void mutex_init_recursive(struct mutex *mtx);
+/*
+ * As mutex_init_pshared(), plus the robust attribute: a process dying while
+ * holding it doesn't leave it locked, but the takeover isn't reported and the
+ * data it protects may be mid update.  Returns false when not available.
+ */
+bool mutex_init_pshared_robust(struct mutex *mtx);
 void mutex_destroy(struct mutex *mtx);
 
 void mutex_lock(struct mutex *mtx) EXCLUSIVE_LOCK_FUNCTION(*mtx);
-- 
2.55.0


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

* [PATCH v3 19/19] perf test: Take turns using the perf mlock budget in the mmaping tests
  2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
                   ` (17 preceding siblings ...)
  2026-10-16  0:18 ` [PATCH v3 18/19] perf mutex: Add a robust, process-shared mutex initializer Arnaldo Carvalho de Melo
@ 2026-10-16  0:18 ` Arnaldo Carvalho de Melo
  2026-10-16  0:28   ` sashiko-bot
  18 siblings, 1 reply; 39+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-10-16  0:18 UTC (permalink / raw)
  To: Namhyung Kim
  Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
	Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
	Arnaldo Carvalho de Melo

From: Arnaldo Carvalho de Melo <acme@redhat.com>

The mmap size used when none is asked for is derived from
kernel.perf_event_mlock_kb itself (see perf_event_mlock_kb_in_pages()),
that after rounding down to a power of two number of pages ends up being
about all of a single CPU's share of that per user budget, so with 'perf
test' starting all the selected tests at once, any further mapping done
while another test still has its ring buffers mapped fails with:

  Permission error mapping pages.
  Consider increasing /proc/sys/kernel/perf_event_mlock_kb,
  or try again with a smaller value of -m/--mmap_pages.

that then shows up as a failure or, worse, as an "unsupp" skip in tests
like the attr ones, i.e. as a false pass.

Instead of making those tests exclusive, which would serialize them
against every other test, keep them running in parallel with the tests
that don't map ring buffers and have them take turns using the budget,
via a robust, process-shared mutex (see mutex_init_pshared_robust()) in
a MAP_SHARED|MAP_ANONYMOUS area that is inherited by the forked test
processes: a test killed by a signal doesn't leave the budget locked,
the kernel marks the mutex owner-died and the next locker takes it over;
a signal that is caught rather than kills ends up the same way, with the
longjmp out of child_test_sig_handler() leaving it held until the child
exits and the kernel hands it over.

Perf's struct mutex is used, so the lock discipline is checked in clang
builds via -Wthread-safety; as the thread safety analysis can't verify
conditionally held locks, the tests that map ring buffers run through a
helper taking the mutex unconditionally around the test function call.

Robust mutexes aren't always available, at build time or, as glibc and
musl can do when get_robust_list(2)/set_robust_list(2) aren't usable, at
run time: in that case the budget is not shared and the tests run as
before, failing or skipping on starvation instead of hanging on a lock
left behind by a test killed by a signal.

Taking turns is unnecessary when nothing can exhaust the budget: once it is
spent, whatever maps on top of it is charged against RLIMIT_MEMLOCK and the
mapping is refused with EPERM when that runs out too, but only for the
processes the kernel is paranoid about and that have no CAP_IPC_LOCK to
lift the check, see perf_mmap_calc_limits().  Root, then, has no turns to
take: it never has a mapping refused, so the turnstile only buys it the
wall clock cost measured below, for a budget nothing is contending for.

The area is shared by the tests of one 'perf test' invocation only: a
second invocation running at the same time maps its own, so the two
don't take turns with each other.

The sysctl is left alone.  Growing it, to leave room for whatever else
the user has running, would need the original value shared between the
invocations, so that the last one out puts it back, and it only helps a
user who can write the sysctl but lacks CAP_IPC_LOCK, the one capability
that lifts the limit.

Raising the soft RLIMIT_MEMLOCK to the hard one when a mapping is refused
is left for a follow-up: it would buy back the wall clock above without
taking turns, where the hard limit allows the raise.

None of this is done when just one of the selected tests maps ring
buffers, as there is nothing to share the budget with in that case.

Taking turns isn't free: a test that hangs while holding the budget now
holds with it every other test mapping ring buffers, where before it
would fail, or skip, by itself.  And there is a cost even when nothing
goes wrong, this being the whole suite on a Ryzen 9 9950X3D, kernel 7.2.7,
32 CPUs, with the default 516 KiB budget, before and after taking turns:

  budget not shared:   42 s wall clock,  72 tests Ok, 28 failed
  tests take turns:   133 s wall clock, 101 tests Ok, 10 failed

Both are non-root runs of the whole series applied, the shell test markings
included, and none of the ten failures is about the budget: eight of them
pass as root, where it can't be exhausted, and the two that fail there too,
the addr2line inline unwinding one, with no branch stack sampling to ask
this machine for, and the BPF metadata collection one, whose output isn't
what the test expects, are not budget related either.

Assisted-by: LLM
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
 tools/perf/Documentation/perf-test.txt |   5 +-
 tools/perf/tests/builtin-test.c        | 129 ++++++++++++++++++++++++-
 2 files changed, 131 insertions(+), 3 deletions(-)

diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt
index 156c6e8caa75f153..f66ccd77bfe749e4 100644
--- a/tools/perf/Documentation/perf-test.txt
+++ b/tools/perf/Documentation/perf-test.txt
@@ -29,8 +29,9 @@ REQUISITES
 ----------
 Shell tests run perf, that maps ring buffers, so they are taken to need room
 in the per user budget set in kernel.perf_event_mlock_kb, shared by the tests
-running at the same time.  Tests state what they need, or waive, in a comma
-separated list in the parens ending their description line, e.g.:
+running at the same time, that take turns using it.  Tests state what they
+need, or waive, in a comma separated list in the parens ending their
+description line, e.g.:
 
 	# perf stat tests (no_mlock)
 
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
index acffa0d0d33d6d30..fb8b6d77b5cdd396 100644
--- a/tools/perf/tests/builtin-test.c
+++ b/tools/perf/tests/builtin-test.c
@@ -32,6 +32,8 @@
 #include <subcmd/parse-options.h>
 #include <subcmd/run-command.h>
 
+#include <sys/mman.h>
+
 #include "builtin.h"
 #include "color.h"
 #include "config.h"
@@ -42,6 +44,9 @@
 #include "symbol.h"
 #include "tests-scripts.h"
 #include "tests.h"
+#include "util/cap.h"
+#include "util/event.h"
+#include "util/mutex.h"
 #include "util/rlimit.h"
 #include "util/strbuf.h"
 #include "util/term.h"
@@ -62,6 +67,17 @@ static bool list_requisites;
 static unsigned int runs_per_test = 1;
 /* Number of lines to include in failure snippet. */
 static unsigned int failure_snippet_lines = 10;
+/*
+ * The tests mapping ring buffers take turns using their share of
+ * kernel.perf_event_mlock_kb via this mutex, shared with the forked test
+ * processes and robust so that a test killed while holding it doesn't leave
+ * the budget locked, see mutex_init_pshared_robust().
+ */
+struct mlock_budget {
+	struct mutex lock;
+};
+
+static struct mlock_budget *mlock_budget;
 const char *dso_to_test;
 const char *test_objdump_path = "objdump";
 static const char *workload_control;
@@ -378,12 +394,30 @@ static void child_test_sig_handler(int sig)
 	siglongjmp(run_test_jmp_buf, sig);
 }
 
+static int run_test_case(struct child_test *child)
+{
+	return test_function(child->test, child->test_case_num)(child->test, child->test_case_num);
+}
+
+/* The thread safety analysis can't express a lock held conditionally. */
+static int run_test_case_mlocked(struct child_test *child)
+{
+	int err;
+
+	mutex_lock(&mlock_budget->lock);
+	err = run_test_case(child);
+	mutex_unlock(&mlock_budget->lock);
+
+	return err;
+}
+
 static int run_test_child(struct child_process *process)
 {
 	const int signals[] = {
 		SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGINT, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM,
 	};
 	struct child_test *child = container_of(process, struct child_test, process);
+	bool needs_mlock = mlock_budget && child->test->needs_mlock;
 	int err;
 
 	close_parent_fds();
@@ -400,7 +434,12 @@ static int run_test_child(struct child_process *process)
 
 	pr_debug("---- start ----\n");
 	pr_debug("test child forked, pid %d\n", getpid());
-	err = test_function(child->test, child->test_case_num)(child->test, child->test_case_num);
+
+	if (needs_mlock)
+		err = run_test_case_mlocked(child);
+	else
+		err = run_test_case(child);
+
 	pr_debug("---- end(%d) ----\n", err);
 
 	check_leaks();
@@ -1776,6 +1815,92 @@ static struct test_suite **build_suites(void)
 #undef for_each_suite
 }
 
+static bool perf_test__shares_mlock_budget(struct test_suite **suites, int argc,
+					   const char *argv[], struct intlist *skiplist)
+{
+	int sharing = 0;
+
+	/* The selection rules mirrored here are __cmd_test()'s: when they change, change both. */
+	for (int curr_suite = 0; suites[curr_suite]; curr_suite++) {
+		struct test_suite *t = suites[curr_suite];
+		int curr_test_case;
+		bool suite_matched;
+
+		if (!t->needs_mlock || intlist__find(skiplist, curr_suite + 1))
+			continue;
+
+		suite_matched = perf_test__matches(test_description(t, /*test_case=*/-1),
+						   curr_suite, argc, argv);
+		test_suite__for_each_test_case(t, curr_test_case) {
+			if (test_exclusive(t, curr_test_case))
+				continue;
+
+			if (suite_matched ||
+			    perf_test__matches(test_description(t, curr_test_case),
+					       curr_suite, argc, argv))
+				sharing += runs_per_test;
+		}
+		if (sharing > 1)
+			return true;
+	}
+	return false;
+}
+
+/*
+ * Once the per user budget set in kernel.perf_event_mlock_kb is spent, what
+ * maps on top of it is charged against RLIMIT_MEMLOCK and the mapping is
+ * refused with EPERM when that runs out too, but only for the processes the
+ * kernel is paranoid about and that have no CAP_IPC_LOCK to lift the check,
+ * see perf_mmap_calc_limits() in the kernel: used by
+ * perf_test__setup_mlock_budget() below, that has nothing to take turns on
+ * when neither applies.
+ */
+static bool perf_test__mlock_budget_exhaustible(void)
+{
+	if (perf_cap__capable(CAP_IPC_LOCK))
+		return false;
+
+	return perf_event_paranoid() > -1;
+}
+
+static void perf_test__setup_mlock_budget(struct test_suite **suites, int argc,
+					  const char *argv[], struct intlist *skiplist)
+{
+	/* Nothing can be denied the budget, nothing to take turns for. */
+	if (!perf_test__mlock_budget_exhaustible()) {
+		pr_debug("Not taking turns using the perf mlock budget: nothing can exhaust it\n");
+		return;
+	}
+
+	/* Nothing runs at the same time, nothing to share the budget with. */
+	if (sequential || !perf_test__shares_mlock_budget(suites, argc, argv, skiplist))
+		return;
+
+	mlock_budget = mmap(NULL, sizeof(*mlock_budget), PROT_READ | PROT_WRITE,
+			    MAP_SHARED | MAP_ANONYMOUS, /*fd=*/-1, /*offset=*/0);
+	if (mlock_budget == MAP_FAILED) {
+		mlock_budget = NULL;
+		pr_err("Failed to map the shared area used to take turns using the perf mlock budget\n");
+		return;
+	}
+
+	if (!mutex_init_pshared_robust(&mlock_budget->lock)) {
+		munmap(mlock_budget, sizeof(*mlock_budget));
+		mlock_budget = NULL;
+		pr_debug("Not taking turns using the perf mlock budget\n");
+	}
+}
+
+static void perf_test__teardown_mlock_budget(void)
+{
+	if (!mlock_budget)
+		return;
+
+	mutex_destroy(&mlock_budget->lock);
+	munmap(mlock_budget, sizeof(*mlock_budget));
+	mlock_budget = NULL;
+}
+
 int cmd_test(int argc, const char **argv)
 {
 	const char *test_usage[] = {
@@ -1884,7 +2009,9 @@ int cmd_test(int argc, const char **argv)
 		intlist__delete(skiplist);
 		return err ? -err : -ENOMEM;
 	}
+	perf_test__setup_mlock_budget(suites, argc, argv, skiplist);
 	ret = __cmd_test(suites, argc, argv, skiplist);
+	perf_test__teardown_mlock_budget();
 	free(suites);
 	intlist__delete(skiplist);
 	return ret;
-- 
2.55.0


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

* Re: [PATCH v3 04/19] perf test attr: Fix default stat metrics expectations
  2026-10-16  0:18 ` [PATCH v3 04/19] perf test attr: Fix default stat metrics expectations Arnaldo Carvalho de Melo
@ 2026-10-16  0:21   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:21 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> Since commit c7adeb0974f1 ("perf jevents: Add set of common metrics based
> on default ones") and commit 2dfc0cab3da4 ("perf stat: Add detail -d,-dd,-ddd
> metrics") the perf stat default metrics open their events in groups, so
> group leaders and members have read_format=15 and members are enabled
> without enable_on_exec, while standalone events keep read_format=3 and
> are disabled until exec.
> 
> Also, since commit b1c5efbfd92e ("perf parse-events: Remove hard coded
> legacy hardware and cache parsing") bare hardware events resolve to the
> core PMU sysfs/JSON events (type 4), so add the raw encodings the Intel
> and AMD core PMUs use for instructions, branch-misses, cpu-cycles and
> stalled-cycles-frontend/backend to test-stat-default and
> test-stat-detailed-1/2/3.
> 
> Add base-stat-group, that accepts both the standalone and the group
> variants of read_format, disabled and enable_on_exec, and point the four
> default/detailed expectations at it.  test-stat-basic, test-stat-C0 and
> test-stat-no-inherit keep base-stat, whose events aren't opened in a
> group: for those, a change that groups or enables them differently is
> still something to catch.  test-stat-group1 does open its events in a
> group, but pins read_format, disabled and enable_on_exec itself, the very
> terms base-stat-group loosens, so it doesn't need it either.
> 
> Fixes: b1c5efbfd92eb84d ("perf parse-events: Remove hard coded legacy hardware and cache parsing")
> Fixes: c7adeb0974f18da4 ("perf jevents: Add set of common metrics based on default ones")
> Fixes: 2dfc0cab3da4a205 ("perf stat: Add detail -d,-dd,-ddd metrics")
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=4


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

* Re: [PATCH v3 01/19] perf test attr: Fix record dwarf sample_type expectation
  2026-10-16  0:18 ` [PATCH v3 01/19] perf test attr: Fix record dwarf sample_type expectation Arnaldo Carvalho de Melo
@ 2026-10-16  0:22   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:22 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> Since commit bdd051e24914 ("perf record: Split --data-mmap option"),
> record_opts__parse_callchain() sets record_data_mmap instead of
> record->sample_address for the DWARF unwind mode, so -d/--data and its
> implied PERF_SAMPLE_DATA_SRC are not enabled implicitly anymore.
> 
> As a result, --call-graph dwarf no longer sets PERF_SAMPLE_ADDR and
> PERF_SAMPLE_DATA_SRC, making the test fail:
> 
>   expected sample_type=45359, got 12583
> 
> where 45359 (0xb12f) is:
> 
>   IP|TID|TIME|ADDR|CALLCHAIN|PERIOD|REGS_USER|STACK_USER|DATA_SRC
> 
> and 12583 (0x3127) is:
> 
>   IP|TID|TIME|CALLCHAIN|PERIOD|REGS_USER|STACK_USER
> 
> i.e. the values differ exactly by PERF_SAMPLE_ADDR (0x8) and
> PERF_SAMPLE_DATA_SRC (0x8000).  The mmap_data=1 expectation is still
> correct as DWARF now enables record_data_mmap.
> 
> This went unnoticed until tests/attr.sh stopped ignoring attr.py's
> return value.
> 
> Fixes: bdd051e249141c79 ("perf record: Split --data-mmap option")
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=1


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

* Re: [PATCH v3 05/19] perf test attr: Relax group checking for ungrouped expectations
  2026-10-16  0:18 ` [PATCH v3 05/19] perf test attr: Relax group checking for ungrouped expectations Arnaldo Carvalho de Melo
@ 2026-10-16  0:23   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:23 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> When matching the recorded events back against the expectations,
> attr.py requires every event opened in a group to match an expectation
> that declares the same group.  The perf stat default metrics open the
> events of each metric in groups, but the test expectations only verify
> the event attributes, so test-stat-default fails with:
> 
>   FAILED '.../test-stat-default' - group failure
> 
> Only enforce the group of a recorded event when the expectation it
> matches declares one.  Expectations that ask for a group are still
> verified in both directions.
> 
> Fixes: c7adeb0974f18da4 ("perf jevents: Add set of common metrics based on default ones")
> Fixes: 2dfc0cab3da4a205 ("perf stat: Add detail -d,-dd,-ddd metrics")
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=5


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

* Re: [PATCH v3 09/19] perf test attr: Introduce mask_bits(), generalizing hide_core_pmu_type()
  2026-10-16  0:18 ` [PATCH v3 09/19] perf test attr: Introduce mask_bits(), generalizing hide_core_pmu_type() Arnaldo Carvalho de Melo
@ 2026-10-16  0:23   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:23 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> What hide_core_pmu_type() does is take a set of bits out of each of the
> '|'-separated alternatives in a value, wildcards included, keeping the
> rest of the expression: extract that as mask_bits() and implement
> hide_core_pmu_type() in terms of it, a prep for using the same thing to
> ignore, when comparing expectations, the sample id bit perf ends up
> asking for and the read_format group bit that a PMU count can remove.
> 
> No change in behaviour.
> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=9


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

* Re: [PATCH v3 12/19] perf test attr: Leave out the events of the other core PMUs
  2026-10-16  0:18 ` [PATCH v3 12/19] perf test attr: Leave out the events of the other core PMUs Arnaldo Carvalho de Melo
@ 2026-10-16  0:24   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:24 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> On a hybrid machine the default stat metrics are opened on each core PMU,
> and the events of the other one have their own encodings: on an i7-14700K
> cpu_atom (type 10) opens UOPS_RETIRED.ALL, config 194, which the
> expectations, written for cpu_core (type 4), don't name:
> 
>   expected type=4, got 10
>   expected config=209|391, got 194
> 
> The test already masks the core PMU type out of 'config' for the legacy
> events, but raw events keep their type and encoding, so leave out the
> events of the core PMUs no expectation names, like the dummy tracking
> event.  Core PMUs are found as perf finds them: 'cpu' or 'cpum_cf', or a
> PMU with a 'cpus' file in sysfs.  Nothing is left out unless an
> expectation names a core PMU, so single core PMU machines and the tests
> with only legacy expectations are not affected.
> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=12


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

* Re: [PATCH v3 14/19] perf test attr: State, spelled out in a line, why a test is skipped
  2026-10-16  0:18 ` [PATCH v3 14/19] perf test attr: State, spelled out in a line, why a test is skipped Arnaldo Carvalho de Melo
@ 2026-10-16  0:24   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:24 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> Per skip the default output shows a self contained one liner:
> 
>   unsupp  'test-record-branch-filter-any' exit 255, expected 1, no attrs recorded to verify
>   skipped [kernel newer than 6.12] 'test-record-group-sampling'
>   skipped [arch = !aarch64, machine = aarch64] 'test-record-graph-default'
> 
> The extra notes explaining the reason, with a copy pasteable command to
> observe the failure, come in the very same stream when the verbosity is
> bumped, at -vvv, nothing is written to a separate place for later
> consultation, it is just the logging ladder:
> 
>   detail: '/root/bin/perf record -o /dev/null --no-bpf-event -j any kill'
>   exited with 255, the expectations want the workload's 1 exit, so there
> [ ... ]
> The command suggested to observe the failure writes to /dev/null, so it
> doesn't leave a perf.data in the directory it is run from.
> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=14


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

* Re: [PATCH v3 13/19] perf test attr: Ignore the group read_format bit the PMU count can remove
  2026-10-16  0:18 ` [PATCH v3 13/19] perf test attr: Ignore the group read_format bit the PMU count can remove Arnaldo Carvalho de Melo
@ 2026-10-16  0:24   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:24 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> read_format's PERF_FORMAT_GROUP bit asks for the group members to be read
> along with the leader, which aside from this bit is verified with the
> fd/group_fd based group checking in Test.compare(), and with more than
> one core PMU a group whose members are pinned to one of them, say
> 'default_core', leaves the clones for the other core PMUs without any
> members:
> 
>   $ perf stat -e '{cycles,default_core/legacy-hardware-config=3/}:S'
>   ... the A53 leader keeps its cohort, both A73 cycles clones are alone,
>       read_format 20 instead of 28
> 
> so that bit isn't there: whether it is there is a consequence of how the
> evlist got expanded to more than one core PMU, not of the groupness the
> [ ... ]
> when the evlist has more than one event even with a single core PMU, keeps
> applying.
> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=13


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

* Re: [PATCH v3 07/19] perf test attr: Ignore the core PMU type perf puts in 'config'
  2026-10-16  0:18 ` [PATCH v3 07/19] perf test attr: Ignore the core PMU type perf puts in 'config' Arnaldo Carvalho de Melo
@ 2026-10-16  0:24   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:24 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> Since commit 55bcf6ef314a ("perf: Extend PERF_TYPE_HARDWARE and
> PERF_TYPE_HW_CACHE") in v5.13, and arm_pmu opted in with commit
> 5c816728651a ("arm_pmu: Add PERF_PMU_CAP_EXTENDED_HW_TYPE capability")
> in v6.6, the kernel can be told which core PMU a legacy type event is
> meant for, with its type in the top 32 bits of 'config', see
> perf_pmus__supports_extended_type(), which __parse_events_add_numeric()
> does for HARDWARE and HW_CACHE events, but only when there is more than
> one core PMU, i.e. on heterogeneous machines, where asking for one event
> ends up being one event per core PMU:
> 
>   $ perf stat -e cycles -a -- sleep 1
>      20,520,998      armv8_cortex_a53/cycles/
>      13,029,350      armv8_cortex_a73/cycles/
> [ ... ]
> keeping what these expectations mean on machines with a single core PMU:
> which of the PMUs is counting is not what we're asking here.
> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=7


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

* Re: [PATCH v3 16/19] perf tests: Mark the C tests mapping perf ring buffers
  2026-10-16  0:18 ` [PATCH v3 16/19] perf tests: Mark the C tests mapping perf ring buffers Arnaldo Carvalho de Melo
@ 2026-10-16  0:24   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:24 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> The C tests that run 'perf record' in the test process itself map ring
> buffers and so need room in the per user budget set in
> kernel.perf_event_mlock_kb: mark them with the new 'needs_mlock' suite
> flag, read by the turnstile added later in the series, so that they
> take turns using that budget while the tests that don't map them keep
> running in parallel.
> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=16


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

* Re: [PATCH v3 11/19] perf test attr: Ignore the dummy tracking event and the terms it carries
  2026-10-16  0:18 ` [PATCH v3 11/19] perf test attr: Ignore the dummy tracking event and the terms it carries Arnaldo Carvalho de Melo
@ 2026-10-16  0:25   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:25 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> With more than one core PMU the requested events can't cover all the
> CPUs, each cluster has its own PMU, so perf opens the software dummy
> event to carry the tracking terms (mmap, comm, task, mmap_data), leaving
> the requested events without them:
> 
>   expected mmap=1, got 0
>   expected comm=1, got 0
>   expected task=1, got 0
> 
> That dummy event is not part of what the expectations are about, so
> leave it out of the comparison, unless the expectations ask for one,
> like system-wide-dummy and test-record-dummy-C0 do.
> 
> And when perf used it to carry the tracking terms, those terms can't be
> matched on the requested events, leave them out of the match as well.
> 
> The terms are left out only where the dropped dummy actually carried them,
> from the sample_type it was opened with, a dummy that didn't is no reason
> to stop verifying them.
> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=11


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

* Re: [PATCH v3 06/19] perf evsel: Mark the event as supported when retrying an open
  2026-10-16  0:18 ` [PATCH v3 06/19] perf evsel: Mark the event as supported when retrying an open Arnaldo Carvalho de Melo
@ 2026-10-16  0:25   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:25 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> Opening a group with perf record on a machine with perf_event_paranoid >
> 1 and no CAP_PERFMON fails the first perf_event_open() with EACCES, after
> which evsel__open_cpu() clears evsel->supported; evsel__fallback() then
> adds exclude_kernel and record__open() retries, and the retry succeeds
> with supported left false.  get_group_fd() treats unsupported events as
> ungroupable, so the retry opens the group members without their leader:
> 
>   sys_perf_event_open: pid 12345 cpu 0 group_fd -1
>   FAILED '.../test-record-group-sampling1' - group failure
> 
> which fails the group subtests of "perf test 82", the Perf attribute
> expectations test, when it is run as a regular user, while it passes as
> root or with CAP_PERFMON, where the first open doesn't fail.
> 
> commit 2cc7aa995ce9 ("perf stat: Refactor retry/skip/fatal error handling")
> dropped the pos->supported = true that record__open() did after a
> successful open and moved the resets into the stat code only, while
> saying "When an event is tried the supported is set to true again".  Do
> that in evsel__open_cpu(), which every open entry point funnels through,
> so that a retry describes its own try instead of the previous failure.
> 
> Fixes: 2cc7aa995ce9ecd8 ("perf stat: Refactor retry/skip/fatal error handling")
> Cc: Ian Rogers <irogers@google.com>
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=6


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

* Re: [PATCH v3 17/19] perf tests: Mark the shell tests not mapping perf ring buffers
  2026-10-16  0:18 ` [PATCH v3 17/19] perf tests: Mark the shell tests not " Arnaldo Carvalho de Melo
@ 2026-10-16  0:26   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:26 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> Shell tests run perf, that maps ring buffers, so they take a turn using
> the per user budget in kernel.perf_event_mlock_kb by default; the ones
> that don't map them, like the ones running just 'perf stat', say
> " (no_mlock)" in their description line and thus keep running in parallel
> with the ones taking turns.
> 
> The 'perf sched stats' tests are among them, they synthesize the samples
> from /proc/schedstat without mapping a ring buffer, and the tests already
> marked " (exclusive)" don't need it, exclusive implies every other
> requisite and keeps them running alone.
> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=17


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

* Re: [PATCH v3 02/19] perf test attr: Allow tests to require a core PMU
  2026-10-16  0:18 ` [PATCH v3 02/19] perf test attr: Allow tests to require a core PMU Arnaldo Carvalho de Melo
@ 2026-10-16  0:26   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:26 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] perf python: memory leak in pyrf__parse_events() on error path
--

commit 8f696c0054607fd944b69ffbf9d282950abfb382
Author: Arnaldo Carvalho de Melo <acme@redhat.com>

perf test attr: Allow tests to require a core PMU

This commit introduces a 'requires' limitation for perf tests to skip
execution if a core PMU is not available. It checks for a core PMU by using
the perf python binding to parse and open the 'cycles' event.

> diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
> index 68f06df9565a0..2d7dd26a57377 100644
> --- a/tools/perf/tests/shell/lib/attr.py
> +++ b/tools/perf/tests/shell/lib/attr.py

[ ... ]

> @@ -28,6 +29,50 @@ def data_equal(a, b):
>  
>      return False
>  
> +CORE_PMU_AVAILABLE = None
> +
> +def core_pmu_available():

[ ... ]

> +def core_pmu_probe():
> +    """
> +    Ask perf itself, through its python binding, to parse and open 'cycles':
> +    a parse failure or an ENOENT means no PMU can count it, any other error
> +    means we can't tell whether there is one.
> +    """
> +    try:
> +        import perf
> +        parse_events = perf.parse_events
> +    except (ImportError, AttributeError):
> +        # Nothing to ask about it, let the expectations speak.
> +        return True
> +
> +    try:
> +        evlist = parse_events('cycles')

[Severity: Low]
Does this trigger a memory leak when parse_events() fails?

The python C wrapper pyrf__parse_events() in tools/perf/util/python.c
appears to omit the required cleanup call on its error path:

tools/perf/util/python.c:pyrf__parse_events() {
	...
	if (parse_events(evlist, input, &err)) {
		parse_events_error__print(&err, input);
		PyErr_SetFromErrno(PyExc_OSError);
		evlist__put(evlist);
		return NULL;
	}
	...
}

While this leak was not introduced by this patch, this new core_pmu_probe()
function actively relies on and triggers this error path to detect the PMU
absence.

Could we clean up the dynamically allocated error strings within the
parse_events_error structure by calling parse_events_error__exit(&err) when
parsing fails?

> +    except OSError:
> +        # No PMU that can count it, the same as the ENOENT below.
> +        return False

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=2

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

* Re: [PATCH v3 15/19] perf tests: Introduce a 'needs_mlock' suite flag
  2026-10-16  0:18 ` [PATCH v3 15/19] perf tests: Introduce a 'needs_mlock' suite flag Arnaldo Carvalho de Melo
@ 2026-10-16  0:27   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:27 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] perf test: missing stdio.h inclusion for musl libc compatibility
--

commit 0cd49c7e0c286b9d1bb7377465b008f3b907a53c
Author: Arnaldo Carvalho de Melo <acme@redhat.com>

perf tests: Introduce a 'needs_mlock' suite flag

This commit adds a 'needs_mlock' flag to 'struct test_suite' to distinguish
test suites that map ring buffers from those that do not, allowing them to
take turns using the kernel.perf_event_mlock_kb budget. It also introduces
a 'requisites' subcommand to list test requirements.

> diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
> index 98134b1c74cf8..acffa0d0d33d6 100644
> --- a/tools/perf/tests/builtin-test.c
> +++ b/tools/perf/tests/builtin-test.c
[ ... ]
> @@ -283,6 +285,30 @@ static const char *test_description(const struct test_suite *t, int test_case)
>  	return t->desc;
>  }
>  
> +static void requisites__fprintf(FILE *fp, const struct test_suite *t, int test_case)
> +{
> +	bool needs_mlock = t->needs_mlock, exclusive;

[Severity: Low]
Since this newly added function explicitly uses the FILE type and fprintf(),
should <stdio.h> be explicitly included at the top of the file to ensure
compatibility with musl libc?

> +
> +	if (test_case < 0)
> +		test_case = test_suite__num_test_cases(t) == 1 ? 0 : -1;
[ ... ]
> diff --git a/tools/perf/tests/tests-scripts.c b/tools/perf/tests/tests-scripts.c
> index 0370bc7013737..0bc84372eae09 100644
> --- a/tools/perf/tests/tests-scripts.c
> +++ b/tools/perf/tests/tests-scripts.c
[ ... ]
> @@ -187,7 +260,8 @@ static void append_script(int dir_fd, const char *name, char *desc,
>  	struct test_suite *test_suite, **result_tmp;
>  	struct test_case *tests;
>  	ssize_t len;
> -	char *exclusive;
> +	/* Shell tests run perf, that maps ring buffers, so they need the budget. */
> +	bool exclusive = false, needs_mlock = true;
>  
>  	snprintf(link, sizeof(link), "/proc/%d/fd/%d", getpid(), dir_fd);

[Severity: Low]
This isn't a bug in the patch logic, but the code relies on snprintf() here.
Would it make sense to explicitly include <stdio.h> in this file as well to
prevent potential build failures on systems using musl libc?

>  	len = readlink(link, filename, sizeof(filename) - 1);
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=15

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

* Re: [PATCH v3 10/19] perf test attr: Accept either sample id bit perf asks for
  2026-10-16  0:18 ` [PATCH v3 10/19] perf test attr: Accept either sample id bit perf asks for Arnaldo Carvalho de Melo
@ 2026-10-16  0:28   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:28 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] perf test: Event.equal() compares event against itself for unrecorded events
- [Medium] perf: false attr test failures from asymmetric config masking
--

commit 26200ca4070ba6631a880adb369c9b57d611baf6
Author: Arnaldo Carvalho de Melo <acme@redhat.com>

perf test attr: Accept either sample id bit perf asks for

evlist__config() asks for a way to tell the samples apart,
PERF_SAMPLE_IDENTIFIER where available, PERF_SAMPLE_ID otherwise. This patch
updates perf test attr to accept either sample id bit when comparing expected
and recorded events by tracking which side is the recorded event.

> diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
> index ee55fbe6f07bd..da4614068772e 100644
> --- a/tools/perf/tests/shell/lib/attr.py
> +++ b/tools/perf/tests/shell/lib/attr.py
[ ... ]
> @@ -198,13 +243,33 @@ class Event(dict):
[ ... ]
>      def equal(self, other):
>          for t in Event.terms:
>              log.debug("      [%s] %s %s", t, self[t], other[t])
>              if t not in self or t not in other:
>                  return False
> -            exp = self.hide_core_pmu_type(t, self[t], other)
> -            got = self.hide_core_pmu_type(t, other[t], other)
> +            recorded = self.recorded_side(other)
> +            expected = other if recorded is self else self
> +            exp = self.hide_core_pmu_type(t, expected[t], recorded or expected)
> +            got = self.hide_core_pmu_type(t, (recorded or self)[t], expected)

[Severity: Medium]
When neither self nor other has the recorded attribute set,
recorded_side(other) will return None. In this case, expected evaluates
to self and got extracts its term from (recorded or self)[t], which is
also self[t].

Does this cause both exp and got to extract from self, ignoring other
entirely and causing the equality check to falsely succeed when comparing two
distinct unrecorded events?

[Severity: Medium]
For got, the third argument passed to hide_core_pmu_type() is hardcoded to
expected.

When self is an expected wildcard event and other is a recorded legacy
PMU event, does this pass (expected, expected) to the masking logic? If so,
could this completely ignore the recorded event's type, resulting in asymmetric
masking of the config field and false test failures?

> +            if recorded is not None:
> +                exp, got = self.hide_event_count_dependent_terms(t, exp, got)
>              if not data_equal(exp, got):
>                  return False
>          return True
> @@ -218,10 +283,15 @@ class Event(dict):
>          for t in Event.terms:
>              if t not in self or t not in other:
>                  continue
> -            exp = self.hide_core_pmu_type(t, self[t], other)
> -            got = self.hide_core_pmu_type(t, other[t], other)
> +            recorded = self.recorded_side(other)
> +            expected = other if recorded is self else self
> +            exp = self.hide_core_pmu_type(t, expected[t], recorded or expected)
> +            got = self.hide_core_pmu_type(t, (recorded or self)[t], expected)

[Severity: Medium]
The same fallback logic appears to be present in Event.diff() as well. Does
passing None for recorded here break the diff logic for unrecorded events by
causing it to diff self against self?

[Severity: Medium]
The third argument for got is again hardcoded to expected instead of
checking the recorded event. Could this asymmetric masking lead to misleading
error logs that report false mismatches for valid legacy PMU configurations?

> +            if recorded is not None:
> +                exp, got = self.hide_event_count_dependent_terms(t, exp, got)
>              if not data_equal(exp, got):
> -                log.warning("expected %s=%s, got %s", t, self[t], other[t])
> +                log.warning("expected %s=%s, got %s", t, expected[t],
> +                            (recorded or self)[t])

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=10

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

* Re: [PATCH v3 19/19] perf test: Take turns using the perf mlock budget in the mmaping tests
  2026-10-16  0:18 ` [PATCH v3 19/19] perf test: Take turns using the perf mlock budget in the mmaping tests Arnaldo Carvalho de Melo
@ 2026-10-16  0:28   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:28 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> The mmap size used when none is asked for is derived from
> kernel.perf_event_mlock_kb itself (see perf_event_mlock_kb_in_pages()),
> that after rounding down to a power of two number of pages ends up being
> about all of a single CPU's share of that per user budget, so with 'perf
> test' starting all the selected tests at once, any further mapping done
> while another test still has its ring buffers mapped fails with:
> 
>   Permission error mapping pages.
>   Consider increasing /proc/sys/kernel/perf_event_mlock_kb,
>   or try again with a smaller value of -m/--mmap_pages.
> 
> that then shows up as a failure or, worse, as an "unsupp" skip in tests
> like the attr ones, i.e. as a false pass.
> [ ... ]
> this machine for, and the BPF metadata collection one, whose output isn't
> what the test expects, are not budget related either.
> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=19


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

* Re: [PATCH v3 18/19] perf mutex: Add a robust, process-shared mutex initializer
  2026-10-16  0:18 ` [PATCH v3 18/19] perf mutex: Add a robust, process-shared mutex initializer Arnaldo Carvalho de Melo
@ 2026-10-16  0:28   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:28 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> A mutex held by a process that can die before unlocking it, like the
> tests forked by 'perf test' that will take turns using the per user
> budget set in kernel.perf_event_mlock_kb, has to be taken over by the
> next locker instead of staying locked forever.
> 
> Robust mutexes, on top of the kernel's robust futexes, do that: when an
> owner dies while holding it the kernel marks the futex word owner-died
> and the next pthread_mutex_lock() returns EOWNERDEAD, then mutex_lock()
> makes it consistent and takes it over; mutex_trylock() and cond_wait()
> come back with EOWNERDEAD too, so they take it over the same way, with
> mutex_trylock() giving the mutex back when it can't be taken over: it
> is held all the same, and a failed trylock must not leak it.
> 
> Add mutex_init_pshared_robust(), returning false when robust mutexes
> aren't available: from libcs without the API, gated by a feature check,
> to musl and glibc refusing them at runtime, when get_robust_list(2) or
> set_robust_list(2) aren't usable.  glibc only sets a flag in
> pthread_mutexattr_setrobust(), refusing the mutex itself in
> pthread_mutex_init() with ENOTSUP instead, other errors from it still
> being reported.  Robust mutexes being unavailable isn't an error, just a
> debug message for whoever then falls back.
> 
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=18


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

* Re: [PATCH v3 08/19] perf evsel: Don't dump probe events to PERF_TEST_ATTR
  2026-10-16  0:18 ` [PATCH v3 08/19] perf evsel: Don't dump probe events to PERF_TEST_ATTR Arnaldo Carvalho de Melo
@ 2026-10-16  0:31   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:31 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: fd leak in evsel__open_cpu() on missing feature fallback
--

commit 4b54c2ca3d287fefa4e358bd15bc06b547bbe8da
Author: Arnaldo Carvalho de Melo <acme@redhat.com>

perf evsel: Don't dump probe events to PERF_TEST_ATTR

This commit prevents support probe events from being dumped into the
$PERF_TEST_ATTR event files. It introduces a support_probe flag to identify
events opened strictly to query supported features.

Link: https://lore.kernel.org/linux-perf-users/asWQvXRdqp_RK85c@google.com/

> diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
> index e61f309e93dcc..59318fef03cb1 100644
> --- a/tools/perf/util/evsel.c
> +++ b/tools/perf/util/evsel.c
> @@ -3104,7 +3104,11 @@ static int evsel__open_cpu(struct evsel *evsel, struct perf_cpu_map *cpus,
>  
>  			bpf_counter__install_pe(evsel, idx, fd);
>  
> -			if (unlikely(test_attr__enabled())) {
> +			/*
> +			 * Support probes aren't what was asked for, leave them
> +			 * out of the PERF_TEST_ATTR dump.
> +			 */
> +			if (unlikely(test_attr__enabled() && !evsel->support_probe)) {

[Severity: Medium]
Does this code leak file descriptors when a missing feature fallback is
triggered on a subsequent loop iteration?

If sys_perf_event_open() succeeds on a capable CPU and records the file
descriptor, but then fails on an incapable CPU, the code jumps to restart
the loop:

evsel__open_cpu() {
    ...
fallback_missing_features:
    evsel__disable_missing_features(evsel);
    ...
    for (idx = start_cpu_map_idx; idx < end_cpu_map_idx; idx++) {
        ...
        for (thread = 0; thread < nthreads; thread++) {
            ...
            fd = sys_perf_event_open(...);
            FD(evsel, idx, thread) = fd;
            if (fd < 0) {
                err = -errno;
                goto try_fallback;
            }
            ...
try_fallback:
    ...
    if (err == -EINVAL && evsel__detect_missing_features(evsel, cpu))
        goto fallback_missing_features;
}

When evsel__detect_missing_features() triggers the jump back to
fallback_missing_features, it appears the loop restarts from
start_cpu_map_idx without routing through out_close to clean up the
previously opened file descriptors. Can this leak the file descriptors when
the array entries are overwritten?

>  				test_attr__open(&evsel->core.attr, pid, cpu,
>  						fd, group_fd, evsel->open_flags);
>  			}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=8

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

* Re: [PATCH v3 03/19] perf test attr: Fix legacy event encodings in group tests
  2026-10-16  0:18 ` [PATCH v3 03/19] perf test attr: Fix legacy event encodings in group tests Arnaldo Carvalho de Melo
@ 2026-10-16  0:32   ` sashiko-bot
  0 siblings, 0 replies; 39+ messages in thread
From: sashiko-bot @ 2026-10-16  0:32 UTC (permalink / raw)
  To: Arnaldo Carvalho de Melo; +Cc: linux-perf-users

> From: Arnaldo Carvalho de Melo <acme@redhat.com>
> 
> Since commit b1c5efbfd92e ("perf parse-events: Remove hard coded legacy
> hardware and cache parsing"), event names are resolved with sysfs/JSON
> priority over the legacy encodings, so bare cache-misses and
> instructions now become the core PMU sysfs/JSON events (on x86 type 4,
> configs 0x412e and 0xc0) rather than the legacy
> PERF_TYPE_HARDWARE/config=3 and config=1 events the tests expect, e.g.:
> 
>   expected config=3, got 0
>   expected disabled=0, got 1
>   expected mmap=0, got 1
>   expected comm=0, got 1
>   expected freq=0, got 1
>   expected enable_on_exec=0, got 1
> [ ... ]
> return value.
> 
> Fixes: b1c5efbfd92eb84d ("perf parse-events: Remove hard coded legacy hardware and cache parsing")
> Assisted-by: LLM
> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261016001843.71978-1-acme@kernel.org?part=3


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

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

Thread overview: 39+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16  0:18 [PATCH v3 0/19] perf test attr: Report the failures again and cope with more than one core PMU Arnaldo Carvalho de Melo
2026-10-16  0:18 ` [PATCH v3 01/19] perf test attr: Fix record dwarf sample_type expectation Arnaldo Carvalho de Melo
2026-10-16  0:22   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 02/19] perf test attr: Allow tests to require a core PMU Arnaldo Carvalho de Melo
2026-10-16  0:26   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 03/19] perf test attr: Fix legacy event encodings in group tests Arnaldo Carvalho de Melo
2026-10-16  0:32   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 04/19] perf test attr: Fix default stat metrics expectations Arnaldo Carvalho de Melo
2026-10-16  0:21   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 05/19] perf test attr: Relax group checking for ungrouped expectations Arnaldo Carvalho de Melo
2026-10-16  0:23   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 06/19] perf evsel: Mark the event as supported when retrying an open Arnaldo Carvalho de Melo
2026-10-16  0:25   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 07/19] perf test attr: Ignore the core PMU type perf puts in 'config' Arnaldo Carvalho de Melo
2026-10-16  0:24   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 08/19] perf evsel: Don't dump probe events to PERF_TEST_ATTR Arnaldo Carvalho de Melo
2026-10-16  0:31   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 09/19] perf test attr: Introduce mask_bits(), generalizing hide_core_pmu_type() Arnaldo Carvalho de Melo
2026-10-16  0:23   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 10/19] perf test attr: Accept either sample id bit perf asks for Arnaldo Carvalho de Melo
2026-10-16  0:28   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 11/19] perf test attr: Ignore the dummy tracking event and the terms it carries Arnaldo Carvalho de Melo
2026-10-16  0:25   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 12/19] perf test attr: Leave out the events of the other core PMUs Arnaldo Carvalho de Melo
2026-10-16  0:24   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 13/19] perf test attr: Ignore the group read_format bit the PMU count can remove Arnaldo Carvalho de Melo
2026-10-16  0:24   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 14/19] perf test attr: State, spelled out in a line, why a test is skipped Arnaldo Carvalho de Melo
2026-10-16  0:24   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 15/19] perf tests: Introduce a 'needs_mlock' suite flag Arnaldo Carvalho de Melo
2026-10-16  0:27   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 16/19] perf tests: Mark the C tests mapping perf ring buffers Arnaldo Carvalho de Melo
2026-10-16  0:24   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 17/19] perf tests: Mark the shell tests not " Arnaldo Carvalho de Melo
2026-10-16  0:26   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 18/19] perf mutex: Add a robust, process-shared mutex initializer Arnaldo Carvalho de Melo
2026-10-16  0:28   ` sashiko-bot
2026-10-16  0:18 ` [PATCH v3 19/19] perf test: Take turns using the perf mlock budget in the mmaping tests Arnaldo Carvalho de Melo
2026-10-16  0:28   ` sashiko-bot

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