perf test attr: Report the failures again and cope with more than one core PMU
Arnaldo Carvalho de Melo
maintainerv2 cover perf test attr: Report the failures again and cope with more than one core PMU
From: Arnaldo Carvalho de Melo <[email protected]>
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 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 + .../build/feature/test-pthread-mutex-robust.c | 19 + tools/perf/Documentation/perf-test.txt | 28 +- tools/perf/Makefile.config | 4 + tools/perf/tests/backward-ring-buffer.c | 2 +- tools/perf/tests/builtin-test.c | 138 +++++- 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 ++ .../tests/shell/attr/test-record-graph-dwarf | 2 +- .../shell/attr/test-record-group-sampling | 5 +- .../shell/attr/test-record-group-sampling1 | 5 +- .../shell/attr/test-record-group-sampling2 | 5 +- .../perf/tests/shell/attr/test-record-group1 | 3 +- .../perf/tests/shell/attr/test-record-group2 | 3 +- .../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 ++-- .../tests/shell/attr/test-stat-detailed-1 | 99 +++-- .../tests/shell/attr/test-stat-detailed-2 | 111 +++-- .../tests/shell/attr/test-stat-detailed-3 | 115 +++-- tools/perf/tests/shell/attr/test-stat-group1 | 3 +- .../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 | 415 ++++++++++++++++-- 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 +- .../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 +- .../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 +- .../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 +- .../perf/tests/shell/test_stat_cpi_python.sh | 2 +- .../tests/shell/test_test_junit_output.sh | 2 +- .../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 | 47 +- tools/perf/tests/tests.h | 17 + tools/perf/util/evsel.c | 12 +- tools/perf/util/evsel.h | 1 + tools/perf/util/mutex.c | 86 +++- tools/perf/util/mutex.h | 5 + tools/perf/util/pfm.c | 1 + tools/perf/util/print-events.c | 1 + 67 files changed, 1092 insertions(+), 258 deletions(-)
base-commit: 3b05843302202598a6d25f3e2543c8182e6a930c
v1-head: b1300831ecc4503d
Arnaldo Carvalho de Melo
maintainerPATCH v2 1/19 perf test attr: Fix record dwarf sample_type expectation
From: Arnaldo Carvalho de Melo <[email protected]>
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.
--- tools/perf/tests/shell/attr/test-record-graph-dwarf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
tools/perf/tests/shell/attr/test-record-graph-dwarf +1 -1
@@ -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
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 01/19] perf test attr: Fix record dwarf sample_type expectation
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 24 lines
From: Arnaldo Carvalho de Melo <[email protected]>
Since commit
bdd051e24914("perf record: Split --data-mmap option"),record_opts__parse_callchain()setsrecord_data_mmapinstead ofrecord->sample_addressfor the DWARF unwind mode, so -d/--data and its impliedPERF_SAMPLE_DATA_SRCare not enabled implicitly anymore.As a result, --call-graph dwarf no longer sets
PERF_SAMPLE_ADDRandPERF_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) andPERF_SAMPLE_DATA_SRC(0x8000). The mmap_data=1 expectation is still correct as DWARF now enablesrecord_data_mmap.
Quoted 6 lines
This went unnoticed until
tests/attr.shstopped ignoring attr.py's return value.Fixes bdd051e249141c79 ("perf record: Split --data-mmap option")Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Arnaldo Carvalho de Melo
maintainerPATCH v2 2/19 perf test attr: Allow tests to require a core PMU
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
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.
--- .../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 | 44 +++++++++++++++++++ 15 files changed, 58 insertions(+)
tools/perf/tests/shell/attr/test-record-group-sampling +1 -0
@@ -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
tools/perf/tests/shell/attr/test-record-group-sampling1 +1 -0
@@ -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
tools/perf/tests/shell/attr/test-record-group-sampling2 +1 -0
@@ -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
tools/perf/tests/shell/attr/test-record-group1 +1 -0
@@ -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
tools/perf/tests/shell/attr/test-record-group2 +1 -0
@@ -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
tools/perf/tests/shell/attr/test-record-group3 +1 -0
@@ -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
tools/perf/tests/shell/attr/test-stat-C0 +1 -0
@@ -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
tools/perf/tests/shell/attr/test-stat-basic +1 -0
@@ -2,6 +2,7 @@ command = stat args = -e cycles kill >/dev/null 2>&1 ret = 1 +requires = core-pmu [event:base-stat] optional=1
tools/perf/tests/shell/attr/test-stat-default +1 -0
@@ -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]
tools/perf/tests/shell/attr/test-stat-detailed-1 +1 -0
@@ -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
tools/perf/tests/shell/attr/test-stat-detailed-2 +1 -0
@@ -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
tools/perf/tests/shell/attr/test-stat-detailed-3 +1 -0
@@ -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
tools/perf/tests/shell/attr/test-stat-group1 +1 -0
@@ -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
tools/perf/tests/shell/attr/test-stat-no-inherit +1 -0
@@ -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
tools/perf/tests/shell/lib/attr.py +44 -0
80 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 02/19] perf test attr: Allow tests to require a core PMU
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] perf python: memory leak of parse_events_error in pyrf__parse_events() --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf python: memory leak of parse_events_error in pyrf__parse_events() --
commit f47ff7c9fc4b5bf17c150eab6410f6c98f01cb7c
Author: Arnaldo Carvalho de Melo <[email protected]>
perf test attr: Allow tests to require a core PMU
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.
Quoted 4 lines
tools/perf/tests/shell/lib/attr.py +0
-0
[ ... ]
Quoted 13 lines
tools/perf/tests/shell/lib/attr.py +9
-0@@ -28,6 +29,34 @@ def data_equal(a, b): return False +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. + """ + try: + import perf + parse_events = perf.parse_events
tools/perf/tests/shell/lib/attr.py def data_equal(a, b): · line 50
Quoted 10 lines
tools/perf/tests/shell/lib/attr.py +10
-0+ 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 +
[Severity: Medium]
Does this expected failure trigger a memory leak of the parse_events_error structure in the underlying C extension?
When parse_events() fails or generates warnings, it dynamically allocates memory for error strings. However, looking at the python bindings in tools/perf/util/python.c:
tools/perf/util/python.c:pyrf__parse_events() {
...
parse_events_error__init(&err);
perf_evlist__set_maps(evlist__core(evlist), cpus, threads);
if (parse_events(evlist, input, &err)) {
parse_events_error__print(&err, input);
PyErr_SetFromErrno(PyExc_OSError);
evlist__put(evlist);
return NULL;
}
result = pyrf_evlist__from_evlist(evlist);
evlist__put(evlist);
return result;
}The err structure is dynamically populated but parse_events_error__exit(&err) is never called on either the success or error paths, leaking memory whenever errors or warnings are generated.
Could pyrf__parse_events() be updated to clean up the error structure?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Arnaldo Carvalho de Melo
maintainerPATCH v2 3/19 perf test attr: Fix legacy event encodings in group tests
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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, introduced for this purpose, 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.
--- 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(-)
tools/perf/tests/shell/attr/README +5 -5
@@ -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)
tools/perf/tests/shell/attr/test-record-group-sampling +2 -2
@@ -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
tools/perf/tests/shell/attr/test-record-group-sampling1 +2 -2
@@ -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
tools/perf/tests/shell/attr/test-record-group-sampling2 +2 -2
@@ -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
tools/perf/tests/shell/attr/test-record-group1 +1 -1
@@ -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
tools/perf/tests/shell/attr/test-record-group2 +1 -1
@@ -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
tools/perf/tests/shell/attr/test-record-group3 +1 -1
@@ -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
tools/perf/tests/shell/attr/test-stat-group1 +1 -1
@@ -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
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 03/19] perf test attr: Fix legacy event encodings in group tests
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 18 lines
From: Arnaldo Carvalho de Melo <[email protected]>
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, configs0x412eand0xc0) 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.
Quoted 3 lines
Fixes b1c5efbfd92eb84d ("perf parse-events: Remove hard coded legacy hardware and cache parsing")Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Arnaldo Carvalho de Melo
maintainerPATCH v2 4/19 perf test attr: Fix default stat metrics expectations
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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 seen on Intel and AMD 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.
--- 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
tools/perf/tests/shell/attr/base-stat-group new file +45 -0
@@ -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
tools/perf/tests/shell/attr/test-stat-default +59 -31
259 lines not on this page · raw message
tools/perf/tests/shell/attr/test-stat-detailed-1 +63 -35
292 lines not on this page · raw message
tools/perf/tests/shell/attr/test-stat-detailed-2 +69 -41
340 lines not on this page · raw message
tools/perf/tests/shell/attr/test-stat-detailed-3 +71 -43
356 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 04/19] perf test attr: Fix default stat metrics expectations
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 25 lines
From: Arnaldo Carvalho de Melo <[email protected]>
Since commit
c7adeb0974f1("perf jevents: Add set of common metrics based on default ones") and commit2dfc0cab3da4("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 withoutenable_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 seen on Intel and AMD 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 andenable_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 pinsread_format, disabled andenable_on_execitself, the very terms base-stat-group loosens, so it doesn't need it either.
Quoted 5 lines
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 LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
Arnaldo Carvalho de Melo
maintainerPATCH v2 5/19 perf test attr: Relax group checking for ungrouped expectations
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
--- tools/perf/tests/shell/lib/attr.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-)
tools/perf/tests/shell/lib/attr.py +7 -4
@@ -353,7 +353,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")
@@ -387,8 +387,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
@@ -397,6 +398,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')
@@ -435,7 +438,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
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 05/19] perf test attr: Relax group checking for ungrouped expectations
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 10 lines
From: Arnaldo Carvalho de Melo <[email protected]>
When matching the recorded events back against the expectations,
attr.pyrequires 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
Quoted 8 lines
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 LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
Arnaldo Carvalho de Melo
maintainerPATCH v2 6/19 perf evsel: Mark the event as supported when retrying an open
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
--- tools/perf/util/evsel.c | 6 ++++++ 1 file changed, 6 insertions(+)
tools/perf/util/evsel.c +6 -0
@@ -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;
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 06/19] perf evsel: Mark the event as supported when retrying an open
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 23 lines
From: Arnaldo Carvalho de Melo <[email protected]>
Opening a group with perf record on a machine with
perf_event_paranoid> 1 and noCAP_PERFMONfails the firstperf_event_open()with EACCES, after whichevsel__open_cpu()clearsevsel->supported;evsel__fallback()then addsexclude_kernelandrecord__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 0group_fd-1 FAILED '.../test-record-group-sampling1' - group failurewhich 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 thepos->supported= true thatrecord__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 inevsel__open_cpu(), which every open entry point funnels through, so that a retry describes its own try instead of the previous failure.
Quoted 4 lines
Fixes 2cc7aa995ce9ecd8 ("perf stat: Refactor retry/skip/fatal error handling")Cc Ian Rogers <[email protected]>Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
Arnaldo Carvalho de Melo
maintainerPATCH v2 7/19 perf test attr: Ignore the core PMU type perf puts in 'config'
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
--- tools/perf/tests/shell/lib/attr.py | 53 ++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-)
tools/perf/tests/shell/lib/attr.py +51 -2
@@ -57,6 +57,32 @@ def core_pmu_available(): 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
@@ -130,12 +156,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
@@ -148,7 +195,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):
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 07/19] perf test attr: Ignore the core PMU type perf puts in 'config'
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] perf: KeyError crash in Event.equal() on missing event terms --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf: KeyError crash in Event.equal() on missing event terms
--
commit 2d6e742f00c1817f447e64e8fed117f4dbcf95c2
Author: Arnaldo Carvalho de Melo <[email protected]>
perf test attr: Ignore the core PMU type perf puts in 'config'
Since commit 55bcf6ef314a in v5.13, and arm_pmu opted in with commit 5c816728651a 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'. This patch takes those bits out when comparing the 'config' of legacy type events so expectations written for the single core PMU case continue matching on heterogeneous machines.
Quoted 4 lines
tools/perf/tests/shell/lib/attr.py +0
-0
[ ... ]
Quoted 4 lines
tools/perf/tests/shell/lib/attr.py +0
-0@@ -130,12 +156,33 @@ class Event(dict): self.add(base) self.add(data)
[ ... ]
Quoted 4 lines
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:
[Severity: Medium]
In Event.equal(), log.debug() accesses self[t] and other[t] before the boundary check executes:
log.debug(" [%s] %s %s", t, self[t], other[t])
if t not in self or t not in other:
Because the Event class inherits from the standard Python dict and does not provide default values for missing keys, will accessing a non-existent key here immediately raise an unhandled KeyError instead of gracefully returning False?
Quoted 8 lines
return False
tools/perf/tests/shell/lib/attr.py +3
-1- 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
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
Arnaldo Carvalho de Melo
maintainerPATCH v2 8/19 perf evsel: Don't dump probe events to PERF_TEST_ATTR
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
--- 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(-)
tools/perf/util/evsel.c +5 -1
@@ -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); }
tools/perf/util/evsel.h +1 -0
@@ -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;
tools/perf/util/pfm.c +1 -0
@@ -141,6 +141,7 @@ static bool is_libpfm_event_supported(const char *name, struct perf_cpu_map *cpureturn false; evsel->is_libpfm_event = true; + evsel->support_probe = true; ret =evsel__open (evsel, cpus, threads);if (ret == -EACCES ) {
tools/perf/util/print-events.c +1 -0
@@ -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) {
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 08/19] perf evsel: Don't dump probe events to PERF_TEST_ATTR
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 17 lines
From: Arnaldo Carvalho de Melo <[email protected]>
Events opened just to ask if they are supported, i.e. the ones in
is_event_supported()andis_libpfm_event_supported(), go through the regularevsel__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
[ ... ]
Quoted 4 lines
Reported-by Namhyung Kim <[email protected]>Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
Arnaldo Carvalho de Melo
maintainerPATCH v2 9/19 perf test attr: Introduce mask_bits(), generalizing hide_core_pmu_type()
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
--- tools/perf/tests/shell/lib/attr.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-)
tools/perf/tests/shell/lib/attr.py +17 -10
@@ -64,24 +64,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):
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 09/19] perf test attr: Introduce mask_bits(), generalizing hide_core_pmu_type()
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines
From: Arnaldo Carvalho de Melo <[email protected]>
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 asmask_bits()and implementhide_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 theread_formatgroup bit that a PMU count can remove.
Quoted 4 lines
No change in behaviour.
Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
Arnaldo Carvalho de Melo
maintainerPATCH v2 10/19 perf test attr: Accept either sample id bit perf asks for
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
--- tools/perf/tests/shell/lib/attr.py | 81 ++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 5 deletions(-)
tools/perf/tests/shell/lib/attr.py +76 -5
@@ -59,6 +59,8 @@ def core_pmu_available(): 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.
@@ -90,6 +92,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
@@ -182,13 +227,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
@@ -202,10 +267,14 @@ 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, exp, got) def parse_version(version): if not version:
@@ -487,6 +556,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)
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 10/19] perf test attr: Accept either sample id bit perf asks for
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 15 lines
From: Arnaldo Carvalho de Melo <[email protected]>
evlist__config()asks for a way to tell the samples apart,PERF_SAMPLE_IDENTIFIERwhere available,PERF_SAMPLE_IDotherwise: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;
Quoted 6 lines
[ ... ]
of which side is the one perf recorded to take the bits out of the right side.Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
Arnaldo Carvalho de Melo
maintainerPATCH v2 11/19 perf test attr: Ignore the dummy tracking event and the terms it carries
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
--- tools/perf/tests/shell/lib/attr.py | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+)
tools/perf/tests/shell/lib/attr.py +56 -0
@@ -61,6 +61,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.
@@ -243,11 +258,26 @@ 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 + + return (getattr(self, 'tracking_moved_to_dummy', False) or + getattr(other, 'tracking_moved_to_dummy', 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)
@@ -267,6 +297,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)
@@ -478,6 +511,27 @@ 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 + + for name, event in list(self.result.items()): + if is_dummy_tracking_event(event): + log.debug(" dropping dummy tracking event [%s]", name) + del self.result[name] + + # The tracking terms got carried by the dropped dummies, flag the + # remaining events so that the match leaves those terms out. + for event in self.result.values(): + event.tracking_moved_to_dummy = True + def compare(self, expect, result, strict_groups=True): match = {}
@@ -559,6 +613,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)
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 11/19] perf test attr: Ignore the dummy tracking event and the terms it carries
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 15 lines
From: Arnaldo Carvalho de Melo <[email protected]>
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 0That 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.
Quoted 5 lines
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.
Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
Arnaldo Carvalho de Melo
maintainerPATCH v2 12/19 perf test attr: Leave out the events of the other core PMUs
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
--- tools/perf/tests/shell/lib/attr.py | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+)
tools/perf/tests/shell/lib/attr.py +39 -0
@@ -532,6 +532,44 @@ class Test(object): for event in self.result.values(): event.tracking_moved_to_dummy = True + 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 = {}
@@ -614,6 +652,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)
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 12/19] perf test attr: Leave out the events of the other core PMUs
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 18 lines
From: Arnaldo Carvalho de Melo <[email protected]>
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 forcpu_core(type 4), don't name:expected type=4, got 10
expected config=209|391, got 194The 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.
Quoted 2 lines
Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
Arnaldo Carvalho de Melo
maintainerPATCH v2 13/19 perf test attr: Ignore the group read_format bit the PMU count can remove
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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 28so 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.
--- tools/perf/tests/shell/lib/attr.py | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+)
tools/perf/tests/shell/lib/attr.py +46 -0
@@ -115,6 +115,50 @@ 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 the kernel qualify 'config' + with the type of the PMU it is for, so this is the number of events perf + makes of 'cycles'. None when there is no binding to ask, as in + core_pmu_available(). + """ + try: + import perf + parse_events = perf.parse_events + except (ImportError, AttributeError): + return None + + try: + return len(parse_events('cycles')) + except OSError: + # No PMU that can count it, see core_pmu_available(). + return None + +SINGLE_CORE_PMU = 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
@@ -245,6 +289,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
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 13/19] perf test attr: Ignore the group read_format bit the PMU count can remove
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 13 lines
From: Arnaldo Carvalho de Melo <[email protected]>
read_format's
PERF_FORMAT_GROUPbit 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 inTest.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
Quoted 8 lines
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 [ ... ]
as whenattr.pyruns withoutsetup_python.sh, the count isn't available and the bit is taken out as well.Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
Arnaldo Carvalho de Melo
maintainerPATCH v2 14/19 perf test attr: State, spelled out in a line, why a test is skipped
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
Per skip the default output shows a self contained one liner:
unsupp 'test-record-branch-filter-any' exit 255, expected 1, likely a missing capability 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 --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 --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.
--- tools/perf/tests/shell/lib/attr.py | 96 ++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 33 deletions(-)
tools/perf/tests/shell/lib/attr.py +63 -33
@@ -209,10 +209,13 @@ class Notest(Exception): return '[%s] \'%s\'' % (self.arch, 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, likely a missing capability' % ( + self.test.path, self.ret, self.expected) class Event(dict): terms = [
@@ -425,13 +428,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 %d.%d" % tuple(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 %d.%d" % tuple(self.kernel_until) def skip_test_auxv(self): def new_auxv(a, pattern):
@@ -447,18 +454,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(',')
@@ -468,24 +477,23 @@ 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' and not core_pmu_available(): log.warning("test limitation 'requires %s'", requirement) - return True + return "needs a core PMU, none here" - return False + return None def restore_sample_rate(self, value=10000): try:
@@ -529,20 +537,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 = %d.%d, this kernel is %s", + self.kernel_since[0], self.kernel_since[1], platform.release()) + raise Notest(self, kernel) + + kernel = self.skip_test_kernel_until() + if kernel: + log.debug(" detail: the test file's kernel_until = %d.%d, this kernel is %s", + self.kernel_until[0], self.kernel_until[1], 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,
@@ -552,10 +576,16 @@ 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 %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): """
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 14/19] perf test attr: State, spelled out in a line, why a test is skipped
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] perf test: TypeError and IndexError if kernel_since or kernel_until has one component --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf test: TypeError and IndexError if kernel_since or kernel_until has one component --
commit 21b41941c9b2a0fd4a3bf8744960357d34dbae9e
Author: Arnaldo Carvalho de Melo <[email protected]>
perf test attr: State, spelled out in a line, why a test is skipped
This patch updates the test skipping logic in the perf test attribute scripts to return descriptive strings instead of boolean values. This enables the test suite to explicitly log why a test is skipped instead of just reporting a generic skip.
Quoted 5 lines
tools/perf/tests/shell/lib/attr.py +0
-0
tools/perf/tests/shell/lib/attr.py class Test(object): · line 434
Quoted 9 lines
tools/perf/tests/shell/lib/attr.py +4
-2@@ -425,13 +428,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 %d.%d" % tuple(self.kernel_since)
[Severity: Medium]
Does this code raise a TypeError if a test specifies a single integer for kernel_since (for example, kernel_since = 6)?
Prior to this patch, Python's native list comparison safely handled 1-element vs 2-element list comparisons without crashing. It looks like formatting a variable-length tuple with a fixed 2-element format string %d.%d will cause a TypeError for 1-element lists.
tools/perf/tests/shell/lib/attr.py class Test(object): · line 441
Quoted 9 lines
tools/perf/tests/shell/lib/attr.py +4
-2def 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 %d.%d" % tuple(self.kernel_until)
[Severity: Medium]
Will this formatting also trigger a TypeError if kernel_until contains only one component?
[ ... ]
Quoted 4 lines
tools/perf/tests/shell/lib/attr.py +0
-0@@ -529,20 +537,36 @@ class Test(object): def run_cmd(self, tempdir): _junk1, _junk2, _junk3, _junk4, myarch = (os.uname())
[ ... ]
tools/perf/tests/shell/lib/attr.py class Test(object): · line 555
Quoted 4 lines
tools/perf/tests/shell/lib/attr.py +4
-0+ kernel = self.skip_test_kernel_since() + if kernel: + log.debug(" detail: the test file's kernel_since = %d.%d, this kernel is %s", + self.kernel_since[0], self.kernel_since[1], platform.release())
[Severity: Medium]
Could directly accessing index 1 via self.kernel_since[1] cause an IndexError when the list only contains a major version?
tools/perf/tests/shell/lib/attr.py class Test(object): · line 561
Quoted 6 lines
tools/perf/tests/shell/lib/attr.py +6
-0+ raise Notest(self, kernel) + + kernel = self.skip_test_kernel_until() + if kernel: + log.debug(" detail: the test file's kernel_until = %d.%d, this kernel is %s", + self.kernel_until[0], self.kernel_until[1], platform.release())
[Severity: Medium]
Similarly, does accessing self.kernel_until[1] raise an IndexError if the test specifies only a single-component version?
Quoted 1 line
+ raise
Notest(self, kernel)
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
Arnaldo Carvalho de Melo
maintainerPATCH v2 15/19 perf tests: Introduce a 'needs_mlock' suite flag
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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 ones that don't map them waive it in the requisites, a comma separated list in the parens ending their description line, e.g.:
# perf stat tests (no_mlock)
"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, "no_mlock" waives the default, and more keywords 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'.
--- tools/perf/Documentation/perf-test.txt | 28 +++++++++++++-- tools/perf/tests/builtin-test.c | 44 +++++++++++++++++++++--- tools/perf/tests/tests-scripts.c | 47 ++++++++++++++++++++++---- tools/perf/tests/tests.h | 17 ++++++++++ 4 files changed, 122 insertions(+), 14 deletions(-)
tools/perf/Documentation/perf-test.txt +25 -3
@@ -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,31 @@ 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, 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) + +"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. "no_mlock" +waives the default, for tests that don't map ring buffers. More keywords +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. + OPTIONS ------- -s::
@@ -36,8 +58,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::
tools/perf/tests/builtin-test.c +39 -5
89 lines not on this page · raw message
tools/perf/tests/tests-scripts.c +41 -6
71 lines not on this page · raw message
tools/perf/tests/tests.h +17 -0
@@ -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);
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 15/19] perf tests: Introduce a 'needs_mlock' suite flag
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 23 lines
From: Arnaldo Carvalho de Melo <[email protected]>
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 aDEFINE_SUITE_NEEDS_MLOCK()variant for the suites using theDEFINE_SUITE()form.Shell tests run perf, that maps ring buffers, so they are taken to need room in that budget by default; the ones that don't map them waive it in the requisites, a comma separated list in the parens ending their description line, e.g.:
# perf stat tests (no_mlock)"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, "
no_mlock" waives the default, and more keywords are to come, each stating what a test needs, till the catch-all isn't needed anymore.
Quoted 5 lines
'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 LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
Arnaldo Carvalho de Melo
maintainerPATCH v2 16/19 perf mutex: Add a robust, process-shared mutex initializer
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
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. Neither is an error, so nothing is printed about them.
--- tools/build/Makefile.feature | 1 + tools/build/feature/Makefile | 4 + tools/build/feature/test-all.c | 5 ++ .../build/feature/test-pthread-mutex-robust.c | 19 ++++ tools/perf/Makefile.config | 4 + tools/perf/util/mutex.c | 86 ++++++++++++++++--- tools/perf/util/mutex.h | 5 ++ 7 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 tools/build/feature/test-pthread-mutex-robust.c
tools/build/Makefile.feature +1 -0
@@ -85,6 +85,7 @@ FEATURE_TESTS_BASIC := \ libcpupower \ pthread-attr-setaffinity-np \ pthread-barrier \ + pthread-mutex-robust \ reallocarray \ stackprotector-all \ timerfd \
tools/build/feature/Makefile +4 -0
@@ -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
tools/build/feature/test-all.c +5 -0
@@ -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 ();
tools/build/feature/test-pthread-mutex-robust.c new file +19 -0
@@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdint.h> +#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 ; +}
tools/perf/Makefile.config +4 -0
@@ -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)
tools/perf/util/mutex.c +74 -12
118 lines not on this page · raw message
tools/perf/util/mutex.h +5 -0
@@ -142,6 +142,11 @@ 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. 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);
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 16/19] perf mutex: Add a robust, process-shared mutex initializer
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] perf/mutex: mutex_trylock and cond_wait break robust mutexes --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] perf/mutex: mutex_trylock and cond_wait break robust mutexes
--
commit be4b6030ad2e50ab9f547f2302595658b05224cb
Author: Arnaldo Carvalho de Melo <[email protected]>
perf mutex: Add a robust, process-shared mutex initializer
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.
Quoted 4 lines
tools/perf/util/mutex.c +0
-0
[ ... ]
Quoted 11 lines
tools/perf/util/mutex.c +7
-0@@ -58,7 +105,22 @@ void mutex_destroy(struct mutex *mtx)void mutex_lock (struct mutex *mtx)NO_THREAD_SAFETY_ANALYSIS { +#ifdef HAVE_PTHREAD_MUTEX_ROBUST +int err =pthread_mutex_lock (&mtx->lock); + +/* +* A robust mutex whose owner died while holding it comes back as +* EOWNERDEAD, marked by the kernel: take it over. +*/
tools/perf/util/mutex.c void mutex_destroy(struct mutex *mtx) · line 124
Quoted 10 lines
tools/perf/util/mutex.c +8
-0+if (err ==EOWNERDEAD ) { +pr_debug ("mutex: taking over one left by a dead owner\n" ); +CHECK_ERR (pthread_mutex_consistent (&mtx->lock)); + }else { +CHECK_ERR (err); + } +#else CHECK_ERR (pthread_mutex_lock (&mtx->lock)); +#endif }
[Severity: Medium]
Since this patch introduces robust mutexes and adds EOWNERDEAD handling to mutex_lock(), does it leave mutex_trylock() and cond_wait() broken when used with them?
If pthread_mutex_trylock() returns EOWNERDEAD, the lock is acquired by the caller, but mutex_trylock() currently treats it as an error:
tools/perf/util/mutex.c:mutex_trylock() {
...
if (ret == 0)
return true; /* Lock acquired. */
if (ret == EBUSY)
return false; /* Lock busy. */
/* Print error. */
CHECK_ERR(ret);
return false;
}This will silently leak the lock and cause the caller to incorrectly assume the lock is not held.
Similarly, if pthread_cond_wait() returns EOWNERDEAD:
tools/perf/util/mutex.c:cond_wait() {
CHECK_ERR(pthread_cond_wait(&cnd->cond, &mtx->lock));
}The mutex is re-acquired but inconsistent. Failing to call pthread_mutex_consistent() before the caller unlocks it will permanently break the mutex.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
Arnaldo Carvalho de Melo
maintainerPATCH v2 17/19 perf test: Take turns using the perf mlock budget in the mmaping tests
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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.
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.
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.
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.
--- tools/perf/tests/builtin-test.c | 94 ++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-)
tools/perf/tests/builtin-test.c +93 -1
@@ -32,6 +32,9 @@#include <subcmd/parse-options.h> #include <subcmd/run-command.h> +#include <signal.h> +#include <sys/mman.h> +#include "builtin.h" #include "color.h" #include "config.h"
@@ -42,6 +45,7 @@#include "symbol.h" #include "tests-scripts.h" #include "tests.h" +#include "util/mutex.h" #include "util/rlimit.h" #include "util/strbuf.h" #include "util/term.h"
@@ -62,6 +66,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 +393,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 +433,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 +1814,59 @@ 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 ; + +/* Nothing to share the budget with if just one test maps ring buffers. */ +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; +} + +static void perf_test__setup_mlock_budget (struct test_suite **suites,int argc, +const char *argv[],struct intlist *skiplist) +{ +/* 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 ("Robust mutexes aren't available, not taking turns using the perf mlock budget\n" ); + } +} +int cmd_test (int argc,const char **argv) {const char *test_usage[] = {
@@ -1884,6 +1975,7 @@ 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);free (suites);intlist__delete (skiplist);
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 17/19] perf test: Take turns using the perf mlock budget in the mmaping tests
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 13 lines
From: Arnaldo Carvalho de Melo <[email protected]>
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.
Quoted 8 lines
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.
[ ... ]
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.Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
Arnaldo Carvalho de Melo
maintainerPATCH v2 18/19 perf tests: Mark the C tests mapping perf ring buffers
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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, see the previous changes, so that they take turns using that budget while the tests that don't map them keep running in parallel.
--- 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(-)
tools/perf/tests/backward-ring-buffer.c +1 -1
@@ -167,4 +167,4 @@ static int test__backward_ring_buffer(struct test_suite *test __maybe_unused, inreturn ret; } -DEFINE_SUITE ("Read backward ring buffer" , backward_ring_buffer); +DEFINE_SUITE_NEEDS_MLOCK ("Read backward ring buffer" , backward_ring_buffer);
tools/perf/tests/code-reading.c +1 -1
@@ -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);
tools/perf/tests/keep-tracking.c +1 -1
@@ -161,4 +161,4 @@ static int test__keep_tracking(struct test_suite *test __maybe_unused, int subtereturn 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);
tools/perf/tests/mmap-basic.c +1 -0
@@ -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, };
tools/perf/tests/openat-syscall-tp-fields.c +1 -0
@@ -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, };
tools/perf/tests/perf-record.c +1 -0
@@ -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, };
tools/perf/tests/perf-time-to-tsc.c +1 -0
@@ -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, };
tools/perf/tests/sw-clock.c +1 -1
@@ -149,4 +149,4 @@ static int test__sw_clock_freq(struct test_suite *test __maybe_unused, int subtereturn ret; } -DEFINE_SUITE ("Software clock events period values" , sw_clock_freq); +DEFINE_SUITE_NEEDS_MLOCK ("Software clock events period values" , sw_clock_freq);
tools/perf/tests/task-exit.c +1 -0
@@ -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, };
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 18/19] perf tests: Mark the C tests mapping perf ring buffers
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 10 lines
From: Arnaldo Carvalho de Melo <[email protected]>
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, see the previous changes, so that they take turns using that budget while the tests that don't map them keep running in parallel.Assisted-by LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=18
Arnaldo Carvalho de Melo
maintainerPATCH v2 19/19 perf tests: Mark the shell tests not mapping perf ring buffers
In reply to Arnaldo Carvalho de MeloFrom: Arnaldo Carvalho de Melo <[email protected]>
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, see the previous changes; 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.
--- 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 | 2 +- tools/perf/tests/shell/test_uprobe_from_different_cu.sh | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-)
tools/perf/tests/shell/drm_pmu.sh +1 -1
@@ -1,5 +1,5 @@ #!/bin/bash -# DRM PMU +# DRM PMU (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e
tools/perf/tests/shell/ftrace.sh +1 -1
@@ -1,5 +1,5 @@ #!/bin/bash -# perf ftrace tests +# perf ftrace tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e
tools/perf/tests/shell/kallsyms.sh +1 -1
@@ -1,5 +1,5 @@ #!/bin/bash -# perf kallsyms tests +# perf kallsyms tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 err=0
tools/perf/tests/shell/list.sh +1 -1
@@ -1,5 +1,5 @@ #!/bin/bash -# perf list tests +# perf list tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e
tools/perf/tests/shell/perf_sched_stats.sh +1 -1
@@ -1,5 +1,5 @@ #!/bin/sh -# perf sched stats tests +# perf sched stats tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e
tools/perf/tests/shell/probe_vfs_getname.sh +1 -1
@@ -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 <[email protected]>, 2017
tools/perf/tests/shell/python-use.sh +1 -1
@@ -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
tools/perf/tests/shell/schedstat_snapshots.sh +1 -1
@@ -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
tools/perf/tests/shell/stat+csv_output.sh +1 -1
@@ -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 ','.
tools/perf/tests/shell/stat+csv_summary.sh +1 -1
@@ -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
tools/perf/tests/shell/stat+event_uniquifying.sh +1 -1
@@ -1,5 +1,5 @@ #!/bin/bash -# perf stat events uniquifying +# perf stat events uniquifying (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e
tools/perf/tests/shell/stat+json_output.sh +1 -1
@@ -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.
tools/perf/tests/shell/stat+shadow_stat.sh +1 -1
@@ -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
tools/perf/tests/shell/stat+std_output.sh +1 -1
@@ -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
tools/perf/tests/shell/stat.sh +1 -1
@@ -1,5 +1,5 @@ #!/bin/bash -# perf stat tests +# perf stat tests (no_mlock) # SPDX-License-Identifier: GPL-2.0 set -e
tools/perf/tests/shell/stat_all_metricgroups.sh +1 -1
@@ -1,5 +1,5 @@ #!/bin/bash -# perf all metricgroups test +# perf all metricgroups test (no_mlock) # SPDX-License-Identifier: GPL-2.0 ParanoidAndNotRoot()
tools/perf/tests/shell/stat_all_metrics.sh +1 -1
@@ -1,5 +1,5 @@ #!/bin/bash -# perf all metrics test +# perf all metrics test (no_mlock) # SPDX-License-Identifier: GPL-2.0 ParanoidAndNotRoot()
tools/perf/tests/shell/stat_all_pfm.sh +1 -1
@@ -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
tools/perf/tests/shell/stat_bpf_counters_cgrp.sh +1 -1
@@ -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
tools/perf/tests/shell/stat_metrics_cgrp.sh +1 -1
@@ -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
tools/perf/tests/shell/stat_metrics_values.sh +1 -1
@@ -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")
tools/perf/tests/shell/test_ilist_python.sh +1 -1
@@ -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
tools/perf/tests/shell/test_stat_cpi_python.sh +1 -1
@@ -1,6 +1,6 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 -# stat-cpi python test +# stat-cpi python test (no_mlock) set -e
tools/perf/tests/shell/test_test_junit_output.sh +1 -1
@@ -1,6 +1,6 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 -# perf test junit XML output validation +# perf test junit XML output validation (no_mlock) set -e
tools/perf/tests/shell/test_uprobe_from_different_cu.sh +1 -1
@@ -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
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v2 19/19] perf tests: Mark the shell tests not mapping perf ring buffers
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 8 lines
From: Arnaldo Carvalho de Melo <[email protected]>
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, see the previous changes; 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.
Quoted 7 lines
The 'perf sched stats' tests are among them, they synthesize the samples from
/proc/schedstatwithout 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 LLMSigned-off-by Arnaldo Carvalho de Melo <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19