summaryrefslogtreecommitdiffstats
path: root/tools/perf/tests/shell
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
commit3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch)
treeb767d7f6e26bc64334d3f14f8422d187239dc45d /tools/perf/tests/shell
downloadlinux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.tar.gz
linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.zip
Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linuxgrafted
Pull io_uring fixes from Jens Axboe: - Fix a task_work add use-after-free with SQPOLL. The sqpoll thread could pop and complete the last request while io_req_normal_work_add() was still looking at them after the mpscq push. Use the same approach as DEFER_TASKRUN to protect from that, holding an RCU read lock across the add, and have exit wait for an RCU grace period for SQPOLL rings as well. - CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED rings - Mark the source filter table as COW when cloning bpf filters, so registering another filter on the source doesn't modify the shared table in place - Initialize the task context before running the BPF loop - Requeue zcrx multishot receives stopped by a local resource - End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit) * tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux: io_uring: fix task_work add use-after-free with SQPOLL io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full io_uring/zcrx: requeue multishot receives stopped by a local resource io_uring: initialize task context before running the BPF loop io_uring: zero big_cqe for aux CQEs on CQE32 rings io_uring: fix free entry check for 32b CQEs on CQE32 rings io_uring: only post the dummy skip CQE on CQE_MIXED rings io_uring/bpf_filter: mark source as COW when cloning filters
Diffstat (limited to 'tools/perf/tests/shell')
-rwxr-xr-xtools/perf/tests/shell/addr2line_inlines.sh96
-rwxr-xr-xtools/perf/tests/shell/amd-ibs-swfilt.sh121
-rwxr-xr-xtools/perf/tests/shell/annotate.sh113
-rwxr-xr-xtools/perf/tests/shell/attr.sh22
-rw-r--r--tools/perf/tests/shell/attr/README71
-rw-r--r--tools/perf/tests/shell/attr/base-record41
-rw-r--r--tools/perf/tests/shell/attr/base-record-spe40
-rw-r--r--tools/perf/tests/shell/attr/base-stat41
-rw-r--r--tools/perf/tests/shell/attr/system-wide-dummy52
-rw-r--r--tools/perf/tests/shell/attr/test-record-C024
-rw-r--r--tools/perf/tests/shell/attr/test-record-basic6
-rw-r--r--tools/perf/tests/shell/attr/test-record-branch-any8
-rw-r--r--tools/perf/tests/shell/attr/test-record-branch-filter-any8
-rw-r--r--tools/perf/tests/shell/attr/test-record-branch-filter-any_call8
-rw-r--r--tools/perf/tests/shell/attr/test-record-branch-filter-any_ret8
-rw-r--r--tools/perf/tests/shell/attr/test-record-branch-filter-hv8
-rw-r--r--tools/perf/tests/shell/attr/test-record-branch-filter-ind_call8
-rw-r--r--tools/perf/tests/shell/attr/test-record-branch-filter-k8
-rw-r--r--tools/perf/tests/shell/attr/test-record-branch-filter-u8
-rw-r--r--tools/perf/tests/shell/attr/test-record-count9
-rw-r--r--tools/perf/tests/shell/attr/test-record-data10
-rw-r--r--tools/perf/tests/shell/attr/test-record-dummy-C055
-rw-r--r--tools/perf/tests/shell/attr/test-record-freq7
-rw-r--r--tools/perf/tests/shell/attr/test-record-graph-default9
-rw-r--r--tools/perf/tests/shell/attr/test-record-graph-default-aarch649
-rw-r--r--tools/perf/tests/shell/attr/test-record-graph-dwarf12
-rw-r--r--tools/perf/tests/shell/attr/test-record-graph-fp9
-rw-r--r--tools/perf/tests/shell/attr/test-record-graph-fp-aarch649
-rw-r--r--tools/perf/tests/shell/attr/test-record-group-sampling40
-rw-r--r--tools/perf/tests/shell/attr/test-record-group-sampling150
-rw-r--r--tools/perf/tests/shell/attr/test-record-group-sampling261
-rw-r--r--tools/perf/tests/shell/attr/test-record-group123
-rw-r--r--tools/perf/tests/shell/attr/test-record-group230
-rw-r--r--tools/perf/tests/shell/attr/test-record-group331
-rw-r--r--tools/perf/tests/shell/attr/test-record-no-buffering9
-rw-r--r--tools/perf/tests/shell/attr/test-record-no-inherit8
-rw-r--r--tools/perf/tests/shell/attr/test-record-no-samples7
-rw-r--r--tools/perf/tests/shell/attr/test-record-period8
-rw-r--r--tools/perf/tests/shell/attr/test-record-pfm-period9
-rw-r--r--tools/perf/tests/shell/attr/test-record-raw7
-rw-r--r--tools/perf/tests/shell/attr/test-record-spe-period12
-rw-r--r--tools/perf/tests/shell/attr/test-record-spe-period-term12
-rw-r--r--tools/perf/tests/shell/attr/test-record-spe-physical-address12
-rw-r--r--tools/perf/tests/shell/attr/test-record-user-regs-no-sve-aarch649
-rw-r--r--tools/perf/tests/shell/attr/test-record-user-regs-old-sve-aarch6410
-rw-r--r--tools/perf/tests/shell/attr/test-record-user-regs-sve-aarch6414
-rw-r--r--tools/perf/tests/shell/attr/test-stat-C010
-rw-r--r--tools/perf/tests/shell/attr/test-stat-basic7
-rw-r--r--tools/perf/tests/shell/attr/test-stat-default236
-rw-r--r--tools/perf/tests/shell/attr/test-stat-detailed-1278
-rw-r--r--tools/perf/tests/shell/attr/test-stat-detailed-2338
-rw-r--r--tools/perf/tests/shell/attr/test-stat-detailed-3358
-rw-r--r--tools/perf/tests/shell/attr/test-stat-group117
-rw-r--r--tools/perf/tests/shell/attr/test-stat-no-inherit8
-rwxr-xr-xtools/perf/tests/shell/base_probe/test_adding_blacklisted.sh114
-rwxr-xr-xtools/perf/tests/shell/base_probe/test_adding_kernel.sh346
-rwxr-xr-xtools/perf/tests/shell/base_probe/test_basic.sh93
-rwxr-xr-xtools/perf/tests/shell/base_probe/test_invalid_options.sh86
-rwxr-xr-xtools/perf/tests/shell/base_probe/test_line_semantics.sh59
-rwxr-xr-xtools/perf/tests/shell/base_report/setup.sh52
-rw-r--r--tools/perf/tests/shell/base_report/stderr-whitelist.txt5
-rwxr-xr-xtools/perf/tests/shell/base_report/test_basic.sh285
-rwxr-xr-xtools/perf/tests/shell/buildid.sh326
-rwxr-xr-xtools/perf/tests/shell/c2c.sh62
-rwxr-xr-xtools/perf/tests/shell/common/check_all_lines_matched.pl39
-rwxr-xr-xtools/perf/tests/shell/common/check_all_patterns_found.pl34
-rwxr-xr-xtools/perf/tests/shell/common/check_errors_whitelisted.pl51
-rwxr-xr-xtools/perf/tests/shell/common/check_no_patterns_found.pl34
-rw-r--r--tools/perf/tests/shell/common/init.sh143
-rw-r--r--tools/perf/tests/shell/common/patterns.sh268
-rw-r--r--tools/perf/tests/shell/common/settings.sh105
-rwxr-xr-xtools/perf/tests/shell/coresight/callchain.sh172
-rwxr-xr-xtools/perf/tests/shell/coresight/concurrent_threads.sh45
-rwxr-xr-xtools/perf/tests/shell/coresight/context_switch_thread.sh69
-rwxr-xr-xtools/perf/tests/shell/coresight/deterministic.sh72
-rwxr-xr-xtools/perf/tests/shell/coresight/raw_dump_stress.sh65
-rwxr-xr-xtools/perf/tests/shell/coresight/test_arm_coresight.sh257
-rwxr-xr-xtools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh64
-rwxr-xr-xtools/perf/tests/shell/daemon.sh538
-rwxr-xr-xtools/perf/tests/shell/data_file_offset_diagnostics.sh91
-rwxr-xr-xtools/perf/tests/shell/data_type_profiling.sh93
-rwxr-xr-xtools/perf/tests/shell/data_validation.sh86
-rwxr-xr-xtools/perf/tests/shell/diff.sh108
-rwxr-xr-xtools/perf/tests/shell/drm_pmu.sh78
-rwxr-xr-xtools/perf/tests/shell/evlist.sh80
-rwxr-xr-xtools/perf/tests/shell/ftrace.sh87
-rwxr-xr-xtools/perf/tests/shell/header.sh74
-rwxr-xr-xtools/perf/tests/shell/inject-callchain.sh45
-rwxr-xr-xtools/perf/tests/shell/inject_aslr.sh539
-rwxr-xr-xtools/perf/tests/shell/jitdump-python.sh108
-rwxr-xr-xtools/perf/tests/shell/kallsyms.sh56
-rwxr-xr-xtools/perf/tests/shell/kvm.sh207
-rwxr-xr-xtools/perf/tests/shell/kwork.sh79
-rw-r--r--tools/perf/tests/shell/lib/attr.py470
-rw-r--r--tools/perf/tests/shell/lib/perf_has_symbol.sh21
-rw-r--r--tools/perf/tests/shell/lib/perf_json_output_lint.py114
-rw-r--r--tools/perf/tests/shell/lib/perf_metric_validation.py606
-rw-r--r--tools/perf/tests/shell/lib/perf_metric_validation_rules.json398
-rw-r--r--tools/perf/tests/shell/lib/perf_record.sh58
-rw-r--r--tools/perf/tests/shell/lib/probe.sh13
-rw-r--r--tools/perf/tests/shell/lib/probe_vfs_getname.sh52
-rw-r--r--tools/perf/tests/shell/lib/setup_python.sh29
-rw-r--r--tools/perf/tests/shell/lib/stat_output.sh195
-rw-r--r--tools/perf/tests/shell/lib/waiting.sh78
-rwxr-xr-xtools/perf/tests/shell/list.sh37
-rwxr-xr-xtools/perf/tests/shell/lock_contention.sh363
-rwxr-xr-xtools/perf/tests/shell/perf-report-hierarchy.sh43
-rwxr-xr-xtools/perf/tests/shell/perf_sched_stats.sh86
-rwxr-xr-xtools/perf/tests/shell/perftool-testsuite_probe.sh24
-rwxr-xr-xtools/perf/tests/shell/perftool-testsuite_report.sh23
-rwxr-xr-xtools/perf/tests/shell/pipe_test.sh127
-rwxr-xr-xtools/perf/tests/shell/probe_vfs_getname.sh25
-rwxr-xr-xtools/perf/tests/shell/python-use.sh36
-rwxr-xr-xtools/perf/tests/shell/record+probe_libc_inet_pton.sh117
-rwxr-xr-xtools/perf/tests/shell/record+script_probe_vfs_getname.sh58
-rwxr-xr-xtools/perf/tests/shell/record+zstd_comp_decomp.sh37
-rwxr-xr-xtools/perf/tests/shell/record+zstd_comp_decomp_multi_record.sh63
-rwxr-xr-xtools/perf/tests/shell/record.sh510
-rwxr-xr-xtools/perf/tests/shell/record_bpf_filter.sh202
-rwxr-xr-xtools/perf/tests/shell/record_lbr.sh194
-rwxr-xr-xtools/perf/tests/shell/record_offcpu.sh174
-rwxr-xr-xtools/perf/tests/shell/record_sideband.sh58
-rwxr-xr-xtools/perf/tests/shell/record_weak_term.sh37
-rwxr-xr-xtools/perf/tests/shell/sched.sh145
-rwxr-xr-xtools/perf/tests/shell/script.sh99
-rwxr-xr-xtools/perf/tests/shell/script_dlfilter.sh107
-rwxr-xr-xtools/perf/tests/shell/script_perl.sh102
-rwxr-xr-xtools/perf/tests/shell/script_python.sh113
-rwxr-xr-xtools/perf/tests/shell/stat+csv_output.sh92
-rwxr-xr-xtools/perf/tests/shell/stat+csv_summary.sh31
-rwxr-xr-xtools/perf/tests/shell/stat+event_uniquifying.sh66
-rwxr-xr-xtools/perf/tests/shell/stat+json_output.sh236
-rwxr-xr-xtools/perf/tests/shell/stat+shadow_stat.sh99
-rwxr-xr-xtools/perf/tests/shell/stat+std_output.sh122
-rwxr-xr-xtools/perf/tests/shell/stat.sh614
-rwxr-xr-xtools/perf/tests/shell/stat_all_metricgroups.sh57
-rwxr-xr-xtools/perf/tests/shell/stat_all_metrics.sh148
-rwxr-xr-xtools/perf/tests/shell/stat_all_pfm.sh51
-rwxr-xr-xtools/perf/tests/shell/stat_all_pmu.sh71
-rwxr-xr-xtools/perf/tests/shell/stat_bpf_counters.sh94
-rwxr-xr-xtools/perf/tests/shell/stat_bpf_counters_cgrp.sh81
-rwxr-xr-xtools/perf/tests/shell/stat_metrics_cgrp.sh200
-rwxr-xr-xtools/perf/tests/shell/stat_metrics_values.sh38
-rwxr-xr-xtools/perf/tests/shell/test_arm_callgraph_fp.sh48
-rwxr-xr-xtools/perf/tests/shell/test_arm_spe.sh143
-rwxr-xr-xtools/perf/tests/shell/test_arm_spe_fork.sh50
-rwxr-xr-xtools/perf/tests/shell/test_bpf_metadata.sh76
-rwxr-xr-xtools/perf/tests/shell/test_brstack.sh273
-rwxr-xr-xtools/perf/tests/shell/test_data_symbol.sh91
-rwxr-xr-xtools/perf/tests/shell/test_dwarf_unwind_multithreaded.sh65
-rwxr-xr-xtools/perf/tests/shell/test_event_open_fallback.sh71
-rwxr-xr-xtools/perf/tests/shell/test_intel_pt.sh558
-rwxr-xr-xtools/perf/tests/shell/test_java_symbol.sh79
-rwxr-xr-xtools/perf/tests/shell/test_perf_data_converter_ctf.sh104
-rwxr-xr-xtools/perf/tests/shell/test_perf_data_converter_json.sh80
-rwxr-xr-xtools/perf/tests/shell/test_stat_intel_tpebs.sh85
-rwxr-xr-xtools/perf/tests/shell/test_task_analyzer.sh182
-rwxr-xr-xtools/perf/tests/shell/test_test_junit_output.sh63
-rwxr-xr-xtools/perf/tests/shell/test_uprobe_from_different_cu.sh89
-rwxr-xr-xtools/perf/tests/shell/timechart.sh73
-rwxr-xr-xtools/perf/tests/shell/top.sh74
-rwxr-xr-xtools/perf/tests/shell/trace+probe_vfs_getname.sh50
-rwxr-xr-xtools/perf/tests/shell/trace_btf_enum.sh79
-rwxr-xr-xtools/perf/tests/shell/trace_btf_general.sh94
-rwxr-xr-xtools/perf/tests/shell/trace_exit_race.sh52
-rwxr-xr-xtools/perf/tests/shell/trace_record_replay.sh51
-rwxr-xr-xtools/perf/tests/shell/trace_summary.sh77
167 files changed, 16960 insertions, 0 deletions
diff --git a/tools/perf/tests/shell/addr2line_inlines.sh b/tools/perf/tests/shell/addr2line_inlines.sh
new file mode 100755
index 000000000..e8754ef2d
--- /dev/null
+++ b/tools/perf/tests/shell/addr2line_inlines.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+# test addr2line inline unwinding
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+test_dir=$(mktemp -d /tmp/perf-test-inline-addr2line.XXXXXXXXXX)
+perf_data="${test_dir}/perf.data"
+perf_script_txt="${test_dir}/perf_script.txt"
+
+cleanup() {
+ rm -rf "${test_dir}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_fp() {
+ echo "Inline unwinding fp verification test"
+ # Record data. Currently only dwarf callchains support inlined functions.
+ perf record --call-graph fp -e task-clock:u -o "${perf_data}" -- perf test -w inlineloop 1
+
+ # Check output with inline (default) and srcline
+ perf script -i "${perf_data}" --fields +srcline > "${perf_script_txt}"
+
+ # Expect the leaf and middle functions to occur on lines in the 20s, with
+ # the non-inlined parent function on a line in the 30s.
+ if grep -q "inlineloop.c:2. (inlined)" "${perf_script_txt}" &&
+ grep -q "inlineloop.c:3.$" "${perf_script_txt}"
+ then
+ echo "Inline unwinding fp verification test [Success]"
+ else
+ echo "Inline unwinding fp verification test [Failed missing inlined functions]"
+ err=1
+ fi
+}
+
+test_dwarf() {
+ echo "Inline unwinding dwarf verification test"
+ # Record data. Currently only dwarf callchains support inlined functions.
+ perf record --call-graph dwarf -e task-clock:u -o "${perf_data}" -- perf test -w inlineloop 1
+
+ # Check output with inline (default) and srcline
+ perf script -i "${perf_data}" --fields +srcline > "${perf_script_txt}"
+
+ # Expect the leaf and middle functions to occur on lines in the 20s, with
+ # the non-inlined parent function on a line in the 30s.
+ if grep -q "inlineloop.c:2. (inlined)" "${perf_script_txt}" &&
+ grep -q "inlineloop.c:3.$" "${perf_script_txt}"
+ then
+ echo "Inline unwinding dwarf verification test [Success]"
+ else
+ echo "Inline unwinding dwarf verification test [Failed missing inlined functions]"
+ err=1
+ fi
+}
+
+test_lbr() {
+ echo "Inline unwinding LBR verification test"
+ if [ ! -f /sys/bus/event_source/devices/cpu/caps/branches ] &&
+ [ ! -f /sys/bus/event_source/devices/cpu_core/caps/branches ]
+ then
+ echo "Skip: only x86 CPUs support LBR"
+ return
+ fi
+
+ # Record data. Currently only dwarf callchains support inlined functions.
+ perf record --call-graph lbr -e cycles:u -o "${perf_data}" -- perf test -w inlineloop 1
+
+ # Check output with inline (default) and srcline
+ perf script -i "${perf_data}" --fields +srcline > "${perf_script_txt}"
+
+ # Expect the leaf and middle functions to occur on lines in the 20s, with
+ # the non-inlined parent function on a line in the 30s.
+ if grep -q "inlineloop.c:2. (inlined)" "${perf_script_txt}" &&
+ grep -q "inlineloop.c:3.$" "${perf_script_txt}"
+ then
+ echo "Inline unwinding lbr verification test [Success]"
+ else
+ echo "Inline unwinding lbr verification test [Failed missing inlined functions]"
+ err=1
+ fi
+}
+
+test_fp
+test_dwarf
+test_lbr
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/amd-ibs-swfilt.sh b/tools/perf/tests/shell/amd-ibs-swfilt.sh
new file mode 100755
index 000000000..5d17dc624
--- /dev/null
+++ b/tools/perf/tests/shell/amd-ibs-swfilt.sh
@@ -0,0 +1,121 @@
+#!/bin/bash
+# AMD IBS software filtering
+
+cpu_family() {
+ grep -m1 '^cpu family[[:space:]]*:' /proc/cpuinfo \
+ | awk -F: '{gsub(/^[ \t]+/, "", $2); print $2}'
+}
+
+cpu_model() {
+ grep -m1 '^model[[:space:]]*:' /proc/cpuinfo \
+ | awk -F: '{gsub(/^[ \t]+/, "", $2); print $2}'
+}
+
+# IBS PMUs does not advertize privilege filtering capability. Rely
+# on Family / Model check.
+hw_priv_filter_supported() {
+ family=$(cpu_family)
+ model=$(cpu_model)
+
+ if (( family > 0x1a )) ||
+ { (( family == 0x1a )) &&
+ { { (( model >= 0x50 && model <= 0x5f )) ||
+ (( model >= 0x80 && model <= 0xaf )) ||
+ (( model >= 0xc0 && model <= 0xcf )); }; }; }; then
+ return 0 # True
+ else
+ return 1 # False
+ fi
+}
+
+ParanoidAndNotRoot() {
+ [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
+}
+
+echo "check availability of IBS swfilt"
+
+# check if IBS PMU is available
+if [ ! -d /sys/bus/event_source/devices/ibs_op ]; then
+ echo "[SKIP] IBS PMU does not exist"
+ exit 2
+fi
+
+# check if IBS PMU has swfilt format
+if [ ! -f /sys/bus/event_source/devices/ibs_op/format/swfilt ]; then
+ echo "[SKIP] IBS PMU does not have swfilt"
+ exit 2
+fi
+
+echo "run perf record with modifier and swfilt"
+err=0
+
+# setting any modifiers should fail
+if ! hw_priv_filter_supported; then
+ perf record -B -e ibs_op//u -o /dev/null true 2> /dev/null
+ if [ $? -eq 0 ]; then
+ echo "[FAIL] IBS PMU should not accept exclude_kernel"
+ exit 1
+ fi
+fi
+
+# setting it with swfilt should be fine
+perf record -B -e ibs_op/swfilt/u -o /dev/null true
+if [ $? -ne 0 ]; then
+ echo "[FAIL] IBS op PMU cannot handle swfilt for exclude_kernel"
+ exit 1
+fi
+
+if ! ParanoidAndNotRoot 1
+then
+ # setting it with swfilt=1 should be fine
+ perf record -B -e ibs_op/swfilt=1/k -o /dev/null true
+ if [ $? -ne 0 ]; then
+ echo "[FAIL] IBS op PMU cannot handle swfilt for exclude_user"
+ exit 1
+ fi
+else
+ echo "[SKIP] not root and perf_event_paranoid too high for exclude_user"
+ err=2
+fi
+
+# check ibs_fetch PMU as well
+perf record -B -e ibs_fetch/swfilt/u -o /dev/null true
+if [ $? -ne 0 ]; then
+ echo "[FAIL] IBS fetch PMU cannot handle swfilt for exclude_kernel"
+ exit 1
+fi
+
+# check system wide recording
+if ! ParanoidAndNotRoot 0
+then
+ perf record -aB --synth=no -e ibs_op/swfilt/k -o /dev/null true
+ if [ $? -ne 0 ]; then
+ echo "[FAIL] IBS op PMU cannot handle swfilt in system-wide mode"
+ exit 1
+ fi
+else
+ echo "[SKIP] not root and perf_event_paranoid too high for system-wide/exclude_user"
+ err=2
+fi
+
+echo "check number of samples with swfilt"
+
+kernel_sample=$(perf record -e ibs_op/swfilt/u -o- true | perf script -i- -F misc | grep -c ^K)
+if [ ${kernel_sample} -ne 0 ]; then
+ echo "[FAIL] unexpected kernel samples: " ${kernel_sample}
+ exit 1
+fi
+
+if ! ParanoidAndNotRoot 1
+then
+ user_sample=$(perf record -e ibs_fetch/swfilt/k -o- true | perf script -i- -F misc | grep -c ^U)
+ if [ ${user_sample} -ne 0 ]; then
+ echo "[FAIL] unexpected user samples: " ${user_sample}
+ exit 1
+ fi
+else
+ echo "[SKIP] not root and perf_event_paranoid too high for exclude_user"
+ err=2
+fi
+
+exit $err
diff --git a/tools/perf/tests/shell/annotate.sh b/tools/perf/tests/shell/annotate.sh
new file mode 100755
index 000000000..689de58e9
--- /dev/null
+++ b/tools/perf/tests/shell/annotate.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+# perf annotate basic tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+shelldir=$(dirname "$0")
+
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+testsym="noploop"
+
+skip_test_missing_symbol ${testsym}
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+perfout=$(mktemp /tmp/__perf_test.perf.out.XXXXX)
+testprog="perf test -w noploop"
+# disassembly format: "percent : offset: instruction (operands ...)"
+disasm_regex="[0-9]*\.[0-9]* *: *\w*: *\w*"
+
+cleanup() {
+ rm -rf "${perfdata}" "${perfout}"
+ rm -rf "${perfdata}".old
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_basic() {
+ mode=$1
+ echo "${mode} perf annotate test"
+ if [ "x${mode}" == "xBasic" ]
+ then
+ perf record -o "${perfdata}" ${testprog} 2> /dev/null
+ else
+ perf record -o - ${testprog} 2> /dev/null > "${perfdata}"
+ fi
+ if [ "x$?" != "x0" ]
+ then
+ echo "${mode} annotate [Failed: perf record]"
+ err=1
+ return
+ fi
+
+ # Generate the annotated output file
+ if [ "x${mode}" == "xBasic" ]
+ then
+ perf annotate --no-demangle -i "${perfdata}" --stdio --percent-limit 10 2> /dev/null > "${perfout}"
+ else
+ perf annotate --no-demangle -i - --stdio 2> /dev/null --percent-limit 10 < "${perfdata}" > "${perfout}"
+ fi
+
+ # check if it has the target symbol
+ if ! grep -q "${testsym}" "${perfout}"
+ then
+ echo "${mode} annotate [Failed: missing target symbol]"
+ cat "${perfout}"
+ err=1
+ return
+ fi
+
+ # check if it has the disassembly lines
+ if ! grep -q "${disasm_regex}" "${perfout}"
+ then
+ echo "${mode} annotate [Failed: missing disasm output from default disassembler]"
+ err=1
+ return
+ fi
+
+ # check again with a target symbol name
+ if [ "x${mode}" == "xBasic" ]
+ then
+ perf annotate --no-demangle -i "${perfdata}" "${testsym}" 2> /dev/null > "${perfout}"
+ else
+ perf annotate --no-demangle -i - "${testsym}" 2> /dev/null < "${perfdata}" > "${perfout}"
+ fi
+
+ if ! head -250 "${perfout}"| grep -q -m 3 "${disasm_regex}"
+ then
+ echo "${mode} annotate [Failed: missing disasm output when specifying the target symbol]"
+ err=1
+ return
+ fi
+
+ # check one more with external objdump tool (forced by --objdump option)
+ if [ "x${mode}" == "xBasic" ]
+ then
+ perf annotate --no-demangle -i "${perfdata}" --percent-limit 10 --objdump=objdump 2> /dev/null > "${perfout}"
+ else
+ perf annotate --no-demangle -i - "${testsym}" --percent-limit 10 --objdump=objdump 2> /dev/null < "${perfdata}" > "${perfout}"
+ fi
+ if ! grep -q -m 3 "${disasm_regex}" "${perfout}"
+ then
+ echo "${mode} annotate [Failed: missing disasm output from non default disassembler (using --objdump)]"
+ err=1
+ return
+ fi
+ echo "${mode} annotate test [Success]"
+}
+
+test_basic Basic
+test_basic Pipe
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/attr.sh b/tools/perf/tests/shell/attr.sh
new file mode 100755
index 000000000..5a4e43b24
--- /dev/null
+++ b/tools/perf/tests/shell/attr.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# Perf attribute expectations test
+# SPDX-License-Identifier: GPL-2.0
+
+err=0
+
+cleanup() {
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+shelldir=$(dirname "$0")
+perf_path=$(which perf)
+python "${shelldir}"/lib/attr.py -d "${shelldir}"/attr -v -p "$perf_path"
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/attr/README b/tools/perf/tests/shell/attr/README
new file mode 100644
index 000000000..67c4ca76b
--- /dev/null
+++ b/tools/perf/tests/shell/attr/README
@@ -0,0 +1,71 @@
+The struct perf_event_attr test (attr tests) support
+====================================================
+This testing support is embedded into perf directly and is governed
+by the PERF_TEST_ATTR environment variable and hook inside the
+sys_perf_event_open function.
+
+The general idea is to store 'struct perf_event_attr' details for
+each event created within single perf command. Each event details
+are stored into separate text file. Once perf command is finished
+these files are checked for values we expect for command.
+
+The attr tests consist of following parts:
+
+tests/attr.c
+------------
+This is the sys_perf_event_open hook implementation. The hook
+is triggered when the PERF_TEST_ATTR environment variable is
+defined. It must contain name of existing directory with access
+and write permissions.
+
+For each sys_perf_event_open call event details are stored in
+separate file. Besides 'struct perf_event_attr' values we also
+store 'fd' and 'group_fd' values to allow checking for groups.
+
+tests/attr.py
+-------------
+This is the python script that does all the hard work. It reads
+the test definition, executes it and checks results.
+
+tests/attr/
+-----------
+Directory containing all attr test definitions.
+Following tests are defined (with perf commands):
+
+ perf record kill (test-record-basic)
+ perf record -b kill (test-record-branch-any)
+ perf record -j any kill (test-record-branch-filter-any)
+ perf record -j any_call kill (test-record-branch-filter-any_call)
+ perf record -j any_ret kill (test-record-branch-filter-any_ret)
+ perf record -j hv kill (test-record-branch-filter-hv)
+ perf record -j ind_call kill (test-record-branch-filter-ind_call)
+ perf record -j k kill (test-record-branch-filter-k)
+ perf record -j u kill (test-record-branch-filter-u)
+ perf record -c 123 kill (test-record-count)
+ perf record -d kill (test-record-data)
+ perf record -F 100 kill (test-record-freq)
+ perf record -g kill (test-record-graph-default)
+ perf record -g kill (test-record-graph-default-aarch64)
+ 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 -D kill (test-record-no-delay)
+ perf record -i kill (test-record-no-inherit)
+ perf record -n kill (test-record-no-samples)
+ perf record -c 100 -P kill (test-record-period)
+ perf record -c 1 --pfm-events=cycles:period=2 (test-record-pfm-period)
+ perf record -R kill (test-record-raw)
+ perf record -c 2 -e arm_spe_0// -- kill (test-record-spe-period)
+ perf record -e arm_spe_0/period=3/ -- kill (test-record-spe-period-term)
+ perf record -e arm_spe_0/pa_enable=1/ -- kill (test-record-spe-physical-address)
+ perf stat -e cycles kill (test-stat-basic)
+ perf stat kill (test-stat-default)
+ 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 -i -e cycles kill (test-stat-no-inherit)
diff --git a/tools/perf/tests/shell/attr/base-record b/tools/perf/tests/shell/attr/base-record
new file mode 100644
index 000000000..b44e4e6e4
--- /dev/null
+++ b/tools/perf/tests/shell/attr/base-record
@@ -0,0 +1,41 @@
+[event]
+fd=1
+group_fd=-1
+# 0 or PERF_FLAG_FD_CLOEXEC flag
+flags=0|8
+cpu=*
+type=0|1
+size=136
+config=0|1
+sample_period=*
+sample_type=263
+read_format=0|4|20
+disabled=1
+inherit=1
+pinned=0
+exclusive=0
+exclude_user=0
+exclude_kernel=0|1
+exclude_hv=0|1
+exclude_idle=0
+mmap=1
+comm=1
+freq=1
+inherit_stat=0
+enable_on_exec=1
+task=1
+watermark=0
+precise_ip=0|1|2|3
+mmap_data=0
+sample_id_all=1
+exclude_host=0|1
+exclude_guest=0|1
+exclude_callchain_kernel=0
+exclude_callchain_user=0
+wakeup_events=0
+bp_type=0
+config1=0
+config2=0
+branch_sample_type=0
+sample_regs_user=0
+sample_stack_user=0
diff --git a/tools/perf/tests/shell/attr/base-record-spe b/tools/perf/tests/shell/attr/base-record-spe
new file mode 100644
index 000000000..08fa96b59
--- /dev/null
+++ b/tools/perf/tests/shell/attr/base-record-spe
@@ -0,0 +1,40 @@
+[event]
+fd=*
+group_fd=-1
+flags=*
+cpu=*
+type=*
+size=*
+config=*
+sample_period=*
+sample_type=*
+read_format=*
+disabled=*
+inherit=*
+pinned=*
+exclusive=*
+exclude_user=*
+exclude_kernel=*
+exclude_hv=*
+exclude_idle=*
+mmap=*
+comm=*
+freq=*
+inherit_stat=*
+enable_on_exec=*
+task=*
+watermark=*
+precise_ip=*
+mmap_data=*
+sample_id_all=*
+exclude_host=*
+exclude_guest=*
+exclude_callchain_kernel=*
+exclude_callchain_user=*
+wakeup_events=*
+bp_type=*
+config1=*
+config2=*
+branch_sample_type=*
+sample_regs_user=*
+sample_stack_user=*
diff --git a/tools/perf/tests/shell/attr/base-stat b/tools/perf/tests/shell/attr/base-stat
new file mode 100644
index 000000000..fccd8ec4d
--- /dev/null
+++ b/tools/perf/tests/shell/attr/base-stat
@@ -0,0 +1,41 @@
+[event]
+fd=1
+group_fd=-1
+# 0 or PERF_FLAG_FD_CLOEXEC flag
+flags=0|8
+cpu=*
+type=0
+size=136
+config=0
+sample_period=0
+sample_type=65536
+read_format=3
+disabled=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=1
+task=0
+watermark=0
+precise_ip=0
+mmap_data=0
+sample_id_all=0
+exclude_host=0|1
+exclude_guest=0|1
+exclude_callchain_kernel=0
+exclude_callchain_user=0
+wakeup_events=0
+bp_type=0
+config1=0
+config2=0
+branch_sample_type=0
+sample_regs_user=0
+sample_stack_user=0
diff --git a/tools/perf/tests/shell/attr/system-wide-dummy b/tools/perf/tests/shell/attr/system-wide-dummy
new file mode 100644
index 000000000..a1e1d6a26
--- /dev/null
+++ b/tools/perf/tests/shell/attr/system-wide-dummy
@@ -0,0 +1,52 @@
+# Event added by system-wide or CPU perf-record to handle the race of
+# processes starting while /proc is processed.
+[event]
+fd=1
+group_fd=-1
+cpu=*
+pid=-1
+flags=8
+type=1
+size=136
+config=9
+sample_period=1
+# PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
+# PERF_SAMPLE_CPU | PERF_SAMPLE_IDENTIFIER
+sample_type=65671
+read_format=4|20
+# Event will be enabled right away.
+disabled=0
+inherit=1
+pinned=0
+exclusive=0
+exclude_user=0
+exclude_kernel=1
+exclude_hv=1
+exclude_idle=0
+mmap=1
+comm=1
+freq=0
+inherit_stat=0
+enable_on_exec=0
+task=1
+watermark=0
+precise_ip=0
+mmap_data=0
+sample_id_all=1
+exclude_host=0
+exclude_guest=1
+exclude_callchain_kernel=0
+exclude_callchain_user=0
+mmap2=1
+comm_exec=1
+context_switch=0
+write_backward=0
+namespaces=0
+use_clockid=0
+wakeup_events=0
+bp_type=0
+config1=0
+config2=0
+branch_sample_type=0
+sample_regs_user=0
+sample_stack_user=0
diff --git a/tools/perf/tests/shell/attr/test-record-C0 b/tools/perf/tests/shell/attr/test-record-C0
new file mode 100644
index 000000000..1049ac8b5
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-C0
@@ -0,0 +1,24 @@
+[config]
+command = record
+args = --no-bpf-event -C 0 kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+cpu=0
+
+# no enable on exec for CPU attached
+enable_on_exec=0
+
+# PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
+# PERF_SAMPLE_PERIOD | PERF_SAMPLE_IDENTIFIER
+# + PERF_SAMPLE_CPU added by -C 0
+sample_type=65927
+
+# Dummy event handles mmaps, comm and task.
+mmap=0
+comm=0
+task=0
+inherit=0
+
+[event:system-wide-dummy]
+inherit=0
diff --git a/tools/perf/tests/shell/attr/test-record-basic b/tools/perf/tests/shell/attr/test-record-basic
new file mode 100644
index 000000000..b0ca42a5e
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-basic
@@ -0,0 +1,6 @@
+[config]
+command = record
+args = --no-bpf-event kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
diff --git a/tools/perf/tests/shell/attr/test-record-branch-any b/tools/perf/tests/shell/attr/test-record-branch-any
new file mode 100644
index 000000000..1a99b3ce6
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-branch-any
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = --no-bpf-event -b kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=2311
+branch_sample_type=8
diff --git a/tools/perf/tests/shell/attr/test-record-branch-filter-any b/tools/perf/tests/shell/attr/test-record-branch-filter-any
new file mode 100644
index 000000000..709768b50
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-branch-filter-any
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = --no-bpf-event -j any kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=2311
+branch_sample_type=8
diff --git a/tools/perf/tests/shell/attr/test-record-branch-filter-any_call b/tools/perf/tests/shell/attr/test-record-branch-filter-any_call
new file mode 100644
index 000000000..f943221f7
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-branch-filter-any_call
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = --no-bpf-event -j any_call kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=2311
+branch_sample_type=16
diff --git a/tools/perf/tests/shell/attr/test-record-branch-filter-any_ret b/tools/perf/tests/shell/attr/test-record-branch-filter-any_ret
new file mode 100644
index 000000000..fd4f5b415
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-branch-filter-any_ret
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = --no-bpf-event -j any_ret kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=2311
+branch_sample_type=32
diff --git a/tools/perf/tests/shell/attr/test-record-branch-filter-hv b/tools/perf/tests/shell/attr/test-record-branch-filter-hv
new file mode 100644
index 000000000..4e52d685e
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-branch-filter-hv
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = --no-bpf-event -j hv kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=2311
+branch_sample_type=8
diff --git a/tools/perf/tests/shell/attr/test-record-branch-filter-ind_call b/tools/perf/tests/shell/attr/test-record-branch-filter-ind_call
new file mode 100644
index 000000000..e08c6ab37
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-branch-filter-ind_call
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = --no-bpf-event -j ind_call kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=2311
+branch_sample_type=64
diff --git a/tools/perf/tests/shell/attr/test-record-branch-filter-k b/tools/perf/tests/shell/attr/test-record-branch-filter-k
new file mode 100644
index 000000000..b4b98f84f
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-branch-filter-k
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = --no-bpf-event -j k kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=2311
+branch_sample_type=8
diff --git a/tools/perf/tests/shell/attr/test-record-branch-filter-u b/tools/perf/tests/shell/attr/test-record-branch-filter-u
new file mode 100644
index 000000000..fb9610edb
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-branch-filter-u
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = --no-bpf-event -j u kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=2311
+branch_sample_type=8
diff --git a/tools/perf/tests/shell/attr/test-record-count b/tools/perf/tests/shell/attr/test-record-count
new file mode 100644
index 000000000..5e9b9019d
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-count
@@ -0,0 +1,9 @@
+[config]
+command = record
+args = --no-bpf-event -c 123 kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_period=123
+sample_type=7
+freq=0
diff --git a/tools/perf/tests/shell/attr/test-record-data b/tools/perf/tests/shell/attr/test-record-data
new file mode 100644
index 000000000..a99bb1314
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-data
@@ -0,0 +1,10 @@
+[config]
+command = record
+args = --no-bpf-event -d kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+# sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
+# PERF_SAMPLE_ADDR | PERF_SAMPLE_PERIOD | PERF_SAMPLE_DATA_SRC
+sample_type=33039
+mmap_data=1
diff --git a/tools/perf/tests/shell/attr/test-record-dummy-C0 b/tools/perf/tests/shell/attr/test-record-dummy-C0
new file mode 100644
index 000000000..91499405f
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-dummy-C0
@@ -0,0 +1,55 @@
+[config]
+command = record
+args = --no-bpf-event -e dummy -C 0 kill >/dev/null 2>&1
+ret = 1
+
+[event]
+fd=1
+group_fd=-1
+cpu=0
+pid=-1
+flags=8
+type=1
+size=136
+config=9
+sample_period=4000
+# PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
+# PERF_SAMPLE_PERIOD
+# + PERF_SAMPLE_CPU added by -C 0
+sample_type=391
+read_format=4|20
+disabled=0
+inherit=0
+pinned=0
+exclusive=0
+exclude_user=0
+exclude_kernel=0
+exclude_hv=0
+exclude_idle=0
+mmap=1
+comm=1
+freq=1
+inherit_stat=0
+enable_on_exec=0
+task=1
+watermark=0
+precise_ip=0
+mmap_data=0
+sample_id_all=1
+exclude_host=0
+exclude_guest=0
+exclude_callchain_kernel=0
+exclude_callchain_user=0
+mmap2=1
+comm_exec=1
+context_switch=0
+write_backward=0
+namespaces=0
+use_clockid=0
+wakeup_events=0
+bp_type=0
+config1=0
+config2=0
+branch_sample_type=0
+sample_regs_user=0
+sample_stack_user=0
diff --git a/tools/perf/tests/shell/attr/test-record-freq b/tools/perf/tests/shell/attr/test-record-freq
new file mode 100644
index 000000000..89e29f6b2
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-freq
@@ -0,0 +1,7 @@
+[config]
+command = record
+args = --no-bpf-event -F 100 kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_period=100
diff --git a/tools/perf/tests/shell/attr/test-record-graph-default b/tools/perf/tests/shell/attr/test-record-graph-default
new file mode 100644
index 000000000..f0a18b4ea
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-graph-default
@@ -0,0 +1,9 @@
+[config]
+command = record
+args = --no-bpf-event -g kill >/dev/null 2>&1
+ret = 1
+# arm64 enables registers in the default mode (fp)
+arch = !aarch64
+
+[event:base-record]
+sample_type=295
diff --git a/tools/perf/tests/shell/attr/test-record-graph-default-aarch64 b/tools/perf/tests/shell/attr/test-record-graph-default-aarch64
new file mode 100644
index 000000000..e98d62efb
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-graph-default-aarch64
@@ -0,0 +1,9 @@
+[config]
+command = record
+args = --no-bpf-event -g kill >/dev/null 2>&1
+ret = 1
+arch = aarch64
+
+[event:base-record]
+sample_type=4391
+sample_regs_user=1073741824
diff --git a/tools/perf/tests/shell/attr/test-record-graph-dwarf b/tools/perf/tests/shell/attr/test-record-graph-dwarf
new file mode 100644
index 000000000..ae92061d6
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-graph-dwarf
@@ -0,0 +1,12 @@
+[config]
+command = record
+args = --no-bpf-event --call-graph dwarf -- kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=45359
+exclude_callchain_user=1
+sample_stack_user=8192
+# TODO different for each arch, no support for that now
+sample_regs_user=*
+mmap_data=1
diff --git a/tools/perf/tests/shell/attr/test-record-graph-fp b/tools/perf/tests/shell/attr/test-record-graph-fp
new file mode 100644
index 000000000..a6e60e839
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-graph-fp
@@ -0,0 +1,9 @@
+[config]
+command = record
+args = --no-bpf-event --call-graph fp kill >/dev/null 2>&1
+ret = 1
+# arm64 enables registers in fp mode
+arch = !aarch64
+
+[event:base-record]
+sample_type=295
diff --git a/tools/perf/tests/shell/attr/test-record-graph-fp-aarch64 b/tools/perf/tests/shell/attr/test-record-graph-fp-aarch64
new file mode 100644
index 000000000..cbeea9971
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-graph-fp-aarch64
@@ -0,0 +1,9 @@
+[config]
+command = record
+args = --no-bpf-event --call-graph fp kill >/dev/null 2>&1
+ret = 1
+arch = aarch64
+
+[event:base-record]
+sample_type=4391
+sample_regs_user=1073741824
diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling b/tools/perf/tests/shell/attr/test-record-group-sampling
new file mode 100644
index 000000000..86a940d78
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-group-sampling
@@ -0,0 +1,40 @@
+[config]
+command = record
+args = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
+ret = 1
+kernel_until = 6.12
+
+[event-1:base-record]
+fd=1
+group_fd=-1
+sample_type=343
+read_format=12|28
+inherit=0
+
+[event-2:base-record]
+fd=2
+group_fd=1
+
+# cache-misses
+type=0
+config=3
+
+# default | PERF_SAMPLE_READ | PERF_SAMPLE_PERIOD
+sample_type=343
+
+# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST
+read_format=12|28
+task=0
+mmap=0
+comm=0
+enable_on_exec=0
+disabled=0
+
+# inherit is disabled for group sampling
+inherit=0
+
+# sampling disabled
+sample_freq=0
+sample_period=0
+freq=0
+write_backward=0
diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling1 b/tools/perf/tests/shell/attr/test-record-group-sampling1
new file mode 100644
index 000000000..4748ab7bf
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-group-sampling1
@@ -0,0 +1,50 @@
+[config]
+command = record
+args = --no-bpf-event -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
+ret = 1
+kernel_since = 6.12
+
+[event-1:base-record]
+fd=1
+group_fd=-1
+
+# cycles
+type=0
+config=0
+
+# default | PERF_SAMPLE_READ | PERF_SAMPLE_PERIOD
+sample_type=343
+
+# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING
+read_format=28|31
+task=1
+mmap=1
+comm=1
+enable_on_exec=1
+disabled=1
+
+# inherit is enabled for group sampling
+inherit=1
+
+[event-2:base-record]
+fd=2
+group_fd=1
+
+# cache-misses
+type=0
+config=3
+
+# default | PERF_SAMPLE_READ | PERF_SAMPLE_PERIOD
+sample_type=343
+
+# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING
+read_format=28|31
+task=0
+mmap=0
+comm=0
+enable_on_exec=0
+disabled=0
+freq=0
+
+# inherit is enabled for group sampling
+inherit=1
diff --git a/tools/perf/tests/shell/attr/test-record-group-sampling2 b/tools/perf/tests/shell/attr/test-record-group-sampling2
new file mode 100644
index 000000000..e0432244a
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-group-sampling2
@@ -0,0 +1,61 @@
+[config]
+command = record
+args = --no-bpf-event -c 10000 -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
+ret = 1
+kernel_since = 6.12
+
+[event-1:base-record]
+fd=1
+group_fd=-1
+
+# cycles
+type=0
+config=0
+
+# default | PERF_SAMPLE_READ
+sample_type=87
+
+# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING
+read_format=28|31
+task=1
+mmap=1
+comm=1
+enable_on_exec=1
+disabled=1
+
+# inherit is enabled for group sampling
+inherit=1
+
+# sampling disabled
+sample_freq=0
+sample_period=10000
+freq=0
+write_backward=0
+
+[event-2:base-record]
+fd=2
+group_fd=1
+
+# cache-misses
+type=0
+config=3
+
+# default | PERF_SAMPLE_READ
+sample_type=87
+
+# PERF_FORMAT_ID | PERF_FORMAT_GROUP | PERF_FORMAT_LOST | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING
+read_format=28|31
+task=0
+mmap=0
+comm=0
+enable_on_exec=0
+disabled=0
+
+# inherit is enabled for group sampling
+inherit=1
+
+# sampling disabled
+sample_freq=0
+sample_period=0
+freq=0
+write_backward=0
diff --git a/tools/perf/tests/shell/attr/test-record-group1 b/tools/perf/tests/shell/attr/test-record-group1
new file mode 100644
index 000000000..eeb1db392
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-group1
@@ -0,0 +1,23 @@
+[config]
+command = record
+args = --no-bpf-event -e '{cycles,instructions}' kill >/dev/null 2>&1
+ret = 1
+
+[event-1:base-record]
+fd=1
+group_fd=-1
+sample_type=327
+read_format=4|20
+
+[event-2:base-record]
+fd=2
+group_fd=1
+type=0
+config=1
+sample_type=327
+read_format=4|20
+mmap=0
+comm=0
+task=0
+enable_on_exec=0
+disabled=0
diff --git a/tools/perf/tests/shell/attr/test-record-group2 b/tools/perf/tests/shell/attr/test-record-group2
new file mode 100644
index 000000000..891d41a7b
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-group2
@@ -0,0 +1,30 @@
+[config]
+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
+
+[event-1:base-record]
+fd=1
+group_fd=-1
+config=0|1
+sample_period=1234000
+sample_type=87
+read_format=12|28
+inherit=0
+freq=0
+
+[event-2:base-record]
+fd=2
+group_fd=1
+config=0|1
+sample_period=6789000
+sample_type=87
+read_format=12|28
+disabled=0
+inherit=0
+mmap=0
+comm=0
+freq=0
+enable_on_exec=0
+task=0
diff --git a/tools/perf/tests/shell/attr/test-record-group3 b/tools/perf/tests/shell/attr/test-record-group3
new file mode 100644
index 000000000..249be8849
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-group3
@@ -0,0 +1,31 @@
+[config]
+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
+
+[event-1:base-record]
+fd=1
+group_fd=-1
+config=0|1
+sample_period=1234000
+sample_type=87
+read_format=28|31
+disabled=1
+inherit=1
+freq=0
+
+[event-2:base-record]
+fd=2
+group_fd=1
+config=0|1
+sample_period=6789000
+sample_type=87
+read_format=28|31
+disabled=0
+inherit=1
+mmap=0
+comm=0
+freq=0
+enable_on_exec=0
+task=0
diff --git a/tools/perf/tests/shell/attr/test-record-no-buffering b/tools/perf/tests/shell/attr/test-record-no-buffering
new file mode 100644
index 000000000..583dcbb07
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-no-buffering
@@ -0,0 +1,9 @@
+[config]
+command = record
+args = --no-bpf-event --no-buffering kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=263
+watermark=0
+wakeup_events=1
diff --git a/tools/perf/tests/shell/attr/test-record-no-inherit b/tools/perf/tests/shell/attr/test-record-no-inherit
new file mode 100644
index 000000000..15d1dc162
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-no-inherit
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = --no-bpf-event -i kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=263
+inherit=0
diff --git a/tools/perf/tests/shell/attr/test-record-no-samples b/tools/perf/tests/shell/attr/test-record-no-samples
new file mode 100644
index 000000000..596fbd6d5
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-no-samples
@@ -0,0 +1,7 @@
+[config]
+command = record
+args = --no-bpf-event -n kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_period=0
diff --git a/tools/perf/tests/shell/attr/test-record-period b/tools/perf/tests/shell/attr/test-record-period
new file mode 100644
index 000000000..119101154
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-period
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = --no-bpf-event -c 100 -P kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_period=100
+freq=0
diff --git a/tools/perf/tests/shell/attr/test-record-pfm-period b/tools/perf/tests/shell/attr/test-record-pfm-period
new file mode 100644
index 000000000..368f5b814
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-pfm-period
@@ -0,0 +1,9 @@
+[config]
+command = record
+args = --no-bpf-event -c 10000 --pfm-events=cycles:period=77777 kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_period=77777
+sample_type=7
+freq=0
diff --git a/tools/perf/tests/shell/attr/test-record-raw b/tools/perf/tests/shell/attr/test-record-raw
new file mode 100644
index 000000000..13a5f7860
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-raw
@@ -0,0 +1,7 @@
+[config]
+command = record
+args = --no-bpf-event -R kill >/dev/null 2>&1
+ret = 1
+
+[event:base-record]
+sample_type=1415
diff --git a/tools/perf/tests/shell/attr/test-record-spe-period b/tools/perf/tests/shell/attr/test-record-spe-period
new file mode 100644
index 000000000..75f8c9cd8
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-spe-period
@@ -0,0 +1,12 @@
+[config]
+command = record
+args = --no-bpf-event -c 2 -e arm_spe_0// -- kill >/dev/null 2>&1
+ret = 1
+arch = aarch64
+
+[event-10:base-record-spe]
+sample_period=2
+freq=0
+
+# dummy event
+[event-1:base-record-spe]
diff --git a/tools/perf/tests/shell/attr/test-record-spe-period-term b/tools/perf/tests/shell/attr/test-record-spe-period-term
new file mode 100644
index 000000000..8f60a4fec
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-spe-period-term
@@ -0,0 +1,12 @@
+[config]
+command = record
+args = --no-bpf-event -e arm_spe_0/period=3/ -- kill >/dev/null 2>&1
+ret = 1
+arch = aarch64
+
+[event-10:base-record-spe]
+sample_period=3
+freq=0
+
+# dummy event
+[event-1:base-record-spe]
diff --git a/tools/perf/tests/shell/attr/test-record-spe-physical-address b/tools/perf/tests/shell/attr/test-record-spe-physical-address
new file mode 100644
index 000000000..7ebcf5012
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-spe-physical-address
@@ -0,0 +1,12 @@
+[config]
+command = record
+args = --no-bpf-event -e arm_spe_0/pa_enable=1/ -- kill >/dev/null 2>&1
+ret = 1
+arch = aarch64
+
+[event-10:base-record-spe]
+# 622727 is the decimal of IP|TID|TIME|CPU|IDENTIFIER|DATA_SRC|PHYS_ADDR
+sample_type=622727
+
+# dummy event
+[event-1:base-record-spe] \ No newline at end of file
diff --git a/tools/perf/tests/shell/attr/test-record-user-regs-no-sve-aarch64 b/tools/perf/tests/shell/attr/test-record-user-regs-no-sve-aarch64
new file mode 100644
index 000000000..bed765450
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-user-regs-no-sve-aarch64
@@ -0,0 +1,9 @@
+# Test that asking for VG fails if the system doesn't support SVE. This
+# applies both before and after the feature was added in 6.1
+[config]
+command = record
+args = --no-bpf-event --user-regs=vg kill >/dev/null 2>&1
+ret = 129
+test_ret = true
+arch = aarch64
+auxv = auxv["AT_HWCAP"] & 0x400000 == 0
diff --git a/tools/perf/tests/shell/attr/test-record-user-regs-old-sve-aarch64 b/tools/perf/tests/shell/attr/test-record-user-regs-old-sve-aarch64
new file mode 100644
index 000000000..15ebfc341
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-user-regs-old-sve-aarch64
@@ -0,0 +1,10 @@
+# Test that asking for VG always fails on old kernels because it was
+# added in 6.1. This applies to systems that either support or don't
+# support SVE.
+[config]
+command = record
+args = --no-bpf-event --user-regs=vg kill >/dev/null 2>&1
+ret = 129
+test_ret = true
+arch = aarch64
+kernel_until = 6.1
diff --git a/tools/perf/tests/shell/attr/test-record-user-regs-sve-aarch64 b/tools/perf/tests/shell/attr/test-record-user-regs-sve-aarch64
new file mode 100644
index 000000000..a65113cd7
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-record-user-regs-sve-aarch64
@@ -0,0 +1,14 @@
+# Test that asking for VG works if the system has SVE and after the
+# feature was added in 6.1
+[config]
+command = record
+args = --no-bpf-event --user-regs=vg kill >/dev/null 2>&1
+ret = 1
+test_ret = true
+arch = aarch64
+auxv = auxv["AT_HWCAP"] & 0x400000 == 0x400000
+kernel_since = 6.1
+
+[event:base-record]
+sample_type=4359
+sample_regs_user=70368744177664
diff --git a/tools/perf/tests/shell/attr/test-stat-C0 b/tools/perf/tests/shell/attr/test-stat-C0
new file mode 100644
index 000000000..a2c76d10b
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-stat-C0
@@ -0,0 +1,10 @@
+[config]
+command = stat
+args = -e cycles -C 0 kill >/dev/null 2>&1
+ret = 1
+
+[event:base-stat]
+# events are disabled by default when attached to cpu
+disabled=1
+enable_on_exec=0
+optional=1
diff --git a/tools/perf/tests/shell/attr/test-stat-basic b/tools/perf/tests/shell/attr/test-stat-basic
new file mode 100644
index 000000000..69867d049
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-stat-basic
@@ -0,0 +1,7 @@
+[config]
+command = stat
+args = -e cycles kill >/dev/null 2>&1
+ret = 1
+
+[event:base-stat]
+optional=1
diff --git a/tools/perf/tests/shell/attr/test-stat-default b/tools/perf/tests/shell/attr/test-stat-default
new file mode 100644
index 000000000..8dd27c1fb
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-stat-default
@@ -0,0 +1,236 @@
+[config]
+command = stat
+args = kill >/dev/null 2>&1
+ret = 1
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
+[event1:base-stat]
+fd=1
+type=1
+config=1
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
+[event2:base-stat]
+fd=2
+type=1
+config=3
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
+[event3:base-stat]
+fd=3
+type=1
+config=4
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
+[event4:base-stat]
+fd=4
+type=1
+config=2
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
+[event5:base-stat]
+fd=5
+type=0
+config=0
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
+[event6:base-stat]
+fd=6
+type=0
+config=7
+optional=1
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
+[event7:base-stat]
+fd=7
+type=0
+config=8
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
+[event8:base-stat]
+fd=8
+type=0
+config=1
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
+[event9:base-stat]
+fd=9
+type=0
+config=4
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
+[event10:base-stat]
+fd=10
+type=0
+config=5
+optional=1
+
+# PERF_TYPE_RAW / slots (0x400)
+[event11:base-stat]
+fd=11
+group_fd=-1
+type=4
+config=1024
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-retiring (0x8000)
+[event12:base-stat]
+fd=12
+group_fd=11
+type=4
+config=32768
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-bad-spec (0x8100)
+[event13:base-stat]
+fd=13
+group_fd=11
+type=4
+config=33024
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-fe-bound (0x8200)
+[event14:base-stat]
+fd=14
+group_fd=11
+type=4
+config=33280
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-be-bound (0x8300)
+[event15:base-stat]
+fd=15
+group_fd=11
+type=4
+config=33536
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-heavy-ops (0x8400)
+[event16:base-stat]
+fd=16
+group_fd=11
+type=4
+config=33792
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-br-mispredict (0x8500)
+[event17:base-stat]
+fd=17
+group_fd=11
+type=4
+config=34048
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-fetch-lat (0x8600)
+[event18:base-stat]
+fd=18
+group_fd=11
+type=4
+config=34304
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-mem-bound (0x8700)
+[event19:base-stat]
+fd=19
+group_fd=11
+type=4
+config=34560
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
+[event20:base-stat]
+fd=20
+type=4
+config=4109
+optional=1
+
+# PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/
+[event21:base-stat]
+fd=21
+type=4
+config=17039629
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD
+[event22:base-stat]
+fd=22
+type=4
+config=60
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY
+[event23:base-stat]
+fd=23
+type=4
+config=2097421
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK
+[event24:base-stat]
+fd=24
+type=4
+config=316
+optional=1
+
+# PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE
+[event25:base-stat]
+fd=25
+type=4
+config=412
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE
+[event26:base-stat]
+fd=26
+type=4
+config=572
+optional=1
+
+# PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS
+[event27:base-stat]
+fd=27
+type=4
+config=706
+optional=1
+
+# PERF_TYPE_RAW / UOPS_ISSUED.ANY
+[event28:base-stat]
+fd=28
+type=4
+config=270
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
+[event29:base-stat]
+fd=29
+type=4
+config=4269
+optional=1 \ No newline at end of file
diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-1 b/tools/perf/tests/shell/attr/test-stat-detailed-1
new file mode 100644
index 000000000..12a2ebf4e
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-stat-detailed-1
@@ -0,0 +1,278 @@
+[config]
+command = stat
+args = -d kill >/dev/null 2>&1
+ret = 1
+
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
+[event1:base-stat]
+fd=1
+type=1
+config=1
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
+[event2:base-stat]
+fd=2
+type=1
+config=3
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
+[event3:base-stat]
+fd=3
+type=1
+config=4
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
+[event4:base-stat]
+fd=4
+type=1
+config=2
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
+[event5:base-stat]
+fd=5
+type=0
+config=0
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
+[event6:base-stat]
+fd=6
+type=0
+config=7
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
+[event7:base-stat]
+fd=7
+type=0
+config=8
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
+[event8:base-stat]
+fd=8
+type=0
+config=1
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
+[event9:base-stat]
+fd=9
+type=0
+config=4
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
+[event10:base-stat]
+fd=10
+type=0
+config=5
+optional=1
+
+# PERF_TYPE_RAW / slots (0x400)
+[event11:base-stat]
+fd=11
+group_fd=-1
+type=4
+config=1024
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-retiring (0x8000)
+[event12:base-stat]
+fd=12
+group_fd=11
+type=4
+config=32768
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-bad-spec (0x8100)
+[event13:base-stat]
+fd=13
+group_fd=11
+type=4
+config=33024
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-fe-bound (0x8200)
+[event14:base-stat]
+fd=14
+group_fd=11
+type=4
+config=33280
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-be-bound (0x8300)
+[event15:base-stat]
+fd=15
+group_fd=11
+type=4
+config=33536
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-heavy-ops (0x8400)
+[event16:base-stat]
+fd=16
+group_fd=11
+type=4
+config=33792
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-br-mispredict (0x8500)
+[event17:base-stat]
+fd=17
+group_fd=11
+type=4
+config=34048
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-fetch-lat (0x8600)
+[event18:base-stat]
+fd=18
+group_fd=11
+type=4
+config=34304
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-mem-bound (0x8700)
+[event19:base-stat]
+fd=19
+group_fd=11
+type=4
+config=34560
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
+[event20:base-stat]
+fd=20
+type=4
+config=4109
+optional=1
+
+# PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/
+[event21:base-stat]
+fd=21
+type=4
+config=17039629
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD
+[event22:base-stat]
+fd=22
+type=4
+config=60
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY
+[event23:base-stat]
+fd=23
+type=4
+config=2097421
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK
+[event24:base-stat]
+fd=24
+type=4
+config=316
+optional=1
+
+# PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE
+[event25:base-stat]
+fd=25
+type=4
+config=412
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE
+[event26:base-stat]
+fd=26
+type=4
+config=572
+optional=1
+
+# PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS
+[event27:base-stat]
+fd=27
+type=4
+config=706
+optional=1
+
+# PERF_TYPE_RAW / UOPS_ISSUED.ANY
+[event28:base-stat]
+fd=28
+type=4
+config=270
+optional=1
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event29:base-stat]
+fd=29
+type=3
+config=0
+optional=1
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event30:base-stat]
+fd=30
+type=3
+config=65536
+optional=1
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event31:base-stat]
+fd=31
+type=3
+config=2
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event32:base-stat]
+fd=32
+type=3
+config=65538
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
+[event33:base-stat]
+fd=33
+type=4
+config=4269
+optional=1 \ No newline at end of file
diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-2 b/tools/perf/tests/shell/attr/test-stat-detailed-2
new file mode 100644
index 000000000..66ea25b7d
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-stat-detailed-2
@@ -0,0 +1,338 @@
+[config]
+command = stat
+args = -dd kill >/dev/null 2>&1
+ret = 1
+
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
+[event1:base-stat]
+fd=1
+type=1
+config=1
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
+[event2:base-stat]
+fd=2
+type=1
+config=3
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
+[event3:base-stat]
+fd=3
+type=1
+config=4
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
+[event4:base-stat]
+fd=4
+type=1
+config=2
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
+[event5:base-stat]
+fd=5
+type=0
+config=0
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
+[event6:base-stat]
+fd=6
+type=0
+config=7
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
+[event7:base-stat]
+fd=7
+type=0
+config=8
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
+[event8:base-stat]
+fd=8
+type=0
+config=1
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
+[event9:base-stat]
+fd=9
+type=0
+config=4
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
+[event10:base-stat]
+fd=10
+type=0
+config=5
+optional=1
+
+# PERF_TYPE_RAW / slots (0x400)
+[event11:base-stat]
+fd=11
+group_fd=-1
+type=4
+config=1024
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-retiring (0x8000)
+[event12:base-stat]
+fd=12
+group_fd=11
+type=4
+config=32768
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-bad-spec (0x8100)
+[event13:base-stat]
+fd=13
+group_fd=11
+type=4
+config=33024
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-fe-bound (0x8200)
+[event14:base-stat]
+fd=14
+group_fd=11
+type=4
+config=33280
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-be-bound (0x8300)
+[event15:base-stat]
+fd=15
+group_fd=11
+type=4
+config=33536
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-heavy-ops (0x8400)
+[event16:base-stat]
+fd=16
+group_fd=11
+type=4
+config=33792
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-br-mispredict (0x8500)
+[event17:base-stat]
+fd=17
+group_fd=11
+type=4
+config=34048
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-fetch-lat (0x8600)
+[event18:base-stat]
+fd=18
+group_fd=11
+type=4
+config=34304
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-mem-bound (0x8700)
+[event19:base-stat]
+fd=19
+group_fd=11
+type=4
+config=34560
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
+[event20:base-stat]
+fd=20
+type=4
+config=4109
+optional=1
+
+# PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/
+[event21:base-stat]
+fd=21
+type=4
+config=17039629
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD
+[event22:base-stat]
+fd=22
+type=4
+config=60
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY
+[event23:base-stat]
+fd=23
+type=4
+config=2097421
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK
+[event24:base-stat]
+fd=24
+type=4
+config=316
+optional=1
+
+# PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE
+[event25:base-stat]
+fd=25
+type=4
+config=412
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE
+[event26:base-stat]
+fd=26
+type=4
+config=572
+optional=1
+
+# PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS
+[event27:base-stat]
+fd=27
+type=4
+config=706
+optional=1
+
+# PERF_TYPE_RAW / UOPS_ISSUED.ANY
+[event28:base-stat]
+fd=28
+type=4
+config=270
+optional=1
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event29:base-stat]
+fd=29
+type=3
+config=0
+optional=1
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event30:base-stat]
+fd=30
+type=3
+config=65536
+optional=1
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event31:base-stat]
+fd=31
+type=3
+config=2
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event32:base-stat]
+fd=32
+type=3
+config=65538
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1I << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event33:base-stat]
+fd=33
+type=3
+config=1
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1I << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event34:base-stat]
+fd=34
+type=3
+config=65537
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_DTLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event35:base-stat]
+fd=35
+type=3
+config=3
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_DTLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event36:base-stat]
+fd=36
+type=3
+config=65539
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_ITLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event37:base-stat]
+fd=37
+type=3
+config=4
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_ITLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event38:base-stat]
+fd=38
+type=3
+config=65540
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
+[event39:base-stat]
+fd=39
+type=4
+config=4269
+optional=1 \ No newline at end of file
diff --git a/tools/perf/tests/shell/attr/test-stat-detailed-3 b/tools/perf/tests/shell/attr/test-stat-detailed-3
new file mode 100644
index 000000000..4a27bbfb9
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-stat-detailed-3
@@ -0,0 +1,358 @@
+[config]
+command = stat
+args = -ddd kill >/dev/null 2>&1
+ret = 1
+
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
+[event1:base-stat]
+fd=1
+type=1
+config=1
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
+[event2:base-stat]
+fd=2
+type=1
+config=3
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
+[event3:base-stat]
+fd=3
+type=1
+config=4
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
+[event4:base-stat]
+fd=4
+type=1
+config=2
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
+[event5:base-stat]
+fd=5
+type=0
+config=0
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
+[event6:base-stat]
+fd=6
+type=0
+config=7
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
+[event7:base-stat]
+fd=7
+type=0
+config=8
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
+[event8:base-stat]
+fd=8
+type=0
+config=1
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
+[event9:base-stat]
+fd=9
+type=0
+config=4
+optional=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
+[event10:base-stat]
+fd=10
+type=0
+config=5
+optional=1
+
+# PERF_TYPE_RAW / slots (0x400)
+[event11:base-stat]
+fd=11
+group_fd=-1
+type=4
+config=1024
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-retiring (0x8000)
+[event12:base-stat]
+fd=12
+group_fd=11
+type=4
+config=32768
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-bad-spec (0x8100)
+[event13:base-stat]
+fd=13
+group_fd=11
+type=4
+config=33024
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-fe-bound (0x8200)
+[event14:base-stat]
+fd=14
+group_fd=11
+type=4
+config=33280
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-be-bound (0x8300)
+[event15:base-stat]
+fd=15
+group_fd=11
+type=4
+config=33536
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-heavy-ops (0x8400)
+[event16:base-stat]
+fd=16
+group_fd=11
+type=4
+config=33792
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-br-mispredict (0x8500)
+[event17:base-stat]
+fd=17
+group_fd=11
+type=4
+config=34048
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-fetch-lat (0x8600)
+[event18:base-stat]
+fd=18
+group_fd=11
+type=4
+config=34304
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / topdown-mem-bound (0x8700)
+[event19:base-stat]
+fd=19
+group_fd=11
+type=4
+config=34560
+disabled=0
+enable_on_exec=0
+read_format=15
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
+[event20:base-stat]
+fd=20
+type=4
+config=4109
+optional=1
+
+# PERF_TYPE_RAW / cpu/INT_MISC.RECOVERY_CYCLES,cmask=1,edge/
+[event21:base-stat]
+fd=21
+type=4
+config=17039629
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.THREAD
+[event22:base-stat]
+fd=22
+type=4
+config=60
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.RECOVERY_CYCLES_ANY
+[event23:base-stat]
+fd=23
+type=4
+config=2097421
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.REF_XCLK
+[event24:base-stat]
+fd=24
+type=4
+config=316
+optional=1
+
+# PERF_TYPE_RAW / IDQ_UOPS_NOT_DELIVERED.CORE
+[event25:base-stat]
+fd=25
+type=4
+config=412
+optional=1
+
+# PERF_TYPE_RAW / CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE
+[event26:base-stat]
+fd=26
+type=4
+config=572
+optional=1
+
+# PERF_TYPE_RAW / UOPS_RETIRED.RETIRE_SLOTS
+[event27:base-stat]
+fd=27
+type=4
+config=706
+optional=1
+
+# PERF_TYPE_RAW / UOPS_ISSUED.ANY
+[event28:base-stat]
+fd=28
+type=4
+config=270
+optional=1
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event29:base-stat]
+fd=29
+type=3
+config=0
+optional=1
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event30:base-stat]
+fd=30
+type=3
+config=65536
+optional=1
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event31:base-stat]
+fd=31
+type=3
+config=2
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event32:base-stat]
+fd=32
+type=3
+config=65538
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1I << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event33:base-stat]
+fd=33
+type=3
+config=1
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1I << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event34:base-stat]
+fd=34
+type=3
+config=65537
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_DTLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event35:base-stat]
+fd=35
+type=3
+config=3
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_DTLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event36:base-stat]
+fd=36
+type=3
+config=65539
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_ITLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event37:base-stat]
+fd=37
+type=3
+config=4
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_ITLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event38:base-stat]
+fd=38
+type=3
+config=65540
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event39:base-stat]
+fd=39
+type=3
+config=512
+optional=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event40:base-stat]
+fd=40
+type=3
+config=66048
+optional=1
+
+# PERF_TYPE_RAW / INT_MISC.UOP_DROPPING
+[event41:base-stat]
+fd=41
+type=4
+config=4269
+optional=1 \ No newline at end of file
diff --git a/tools/perf/tests/shell/attr/test-stat-group1 b/tools/perf/tests/shell/attr/test-stat-group1
new file mode 100644
index 000000000..174675112
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-stat-group1
@@ -0,0 +1,17 @@
+[config]
+command = stat
+args = -e '{cycles,instructions}' kill >/dev/null 2>&1
+ret = 1
+
+[event-1:base-stat]
+fd=1
+group_fd=-1
+read_format=3|15
+
+[event-2:base-stat]
+fd=2
+group_fd=1
+config=1
+disabled=0
+enable_on_exec=0
+read_format=3|15
diff --git a/tools/perf/tests/shell/attr/test-stat-no-inherit b/tools/perf/tests/shell/attr/test-stat-no-inherit
new file mode 100644
index 000000000..924fbb930
--- /dev/null
+++ b/tools/perf/tests/shell/attr/test-stat-no-inherit
@@ -0,0 +1,8 @@
+[config]
+command = stat
+args = -i -e cycles kill >/dev/null 2>&1
+ret = 1
+
+[event:base-stat]
+inherit=0
+optional=1
diff --git a/tools/perf/tests/shell/base_probe/test_adding_blacklisted.sh b/tools/perf/tests/shell/base_probe/test_adding_blacklisted.sh
new file mode 100755
index 000000000..f74aab5c5
--- /dev/null
+++ b/tools/perf/tests/shell/base_probe/test_adding_blacklisted.sh
@@ -0,0 +1,114 @@
+#!/bin/bash
+# perf_probe :: Reject blacklisted probes (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+#
+# test_adding_blacklisted of perf_probe test
+# Author: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
+# Author: Michael Petlan <mpetlan@redhat.com>
+#
+# Description:
+#
+# Blacklisted functions should not be added successfully as probes,
+# they must be skipped.
+#
+
+DIR_PATH="$(dirname $0)"
+TEST_RESULT=0
+
+# include working environment
+. "$DIR_PATH/../common/init.sh"
+
+# skip if not supported
+BLACKFUNC_LIST=`head -n 5 /sys/kernel/debug/kprobes/blacklist 2> /dev/null | cut -f2`
+if [ -z "$BLACKFUNC_LIST" ]; then
+ print_overall_skipped
+ exit 2
+fi
+
+# try to find vmlinux with DWARF debug info
+VMLINUX_FILE=$(perf probe -v random_probe |& grep "Using.*for symbols" | sed -r 's/^Using (.*) for symbols$/\1/')
+
+# remove all previously added probes
+clear_all_probes
+
+
+### adding blacklisted function
+REGEX_SCOPE_FAIL="Failed to find scope of probe point"
+REGEX_SKIP_MESSAGE=" is blacklisted function, skip it\."
+REGEX_NOT_FOUND_MESSAGE="Probe point \'$RE_EVENT\' not found."
+REGEX_ERROR_MESSAGE="Error: Failed to add events."
+REGEX_INVALID_ARGUMENT="Failed to write event: Invalid argument"
+REGEX_SYMBOL_FAIL="Failed to find symbol at $RE_ADDRESS"
+REGEX_OUT_SECTION="$RE_EVENT is out of \.\w+, skip it"
+REGEX_MISSING_DECL_LINE="A function DIE doesn't have decl_line. Maybe broken DWARF?"
+
+BLACKFUNC=""
+SKIP_DWARF=0
+
+for BLACKFUNC in $BLACKFUNC_LIST; do
+ echo "Probing $BLACKFUNC"
+
+ # functions from blacklist should be skipped by perf probe
+ ! $CMD_PERF probe $BLACKFUNC > $LOGS_DIR/adding_blacklisted.log 2> $LOGS_DIR/adding_blacklisted.err
+ PERF_EXIT_CODE=$?
+
+ # check for bad DWARF polluting the result
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "$REGEX_MISSING_DECL_LINE" >/dev/null < $LOGS_DIR/adding_blacklisted.err
+
+ if [ $? -eq 0 ]; then
+ SKIP_DWARF=1
+ echo "Result polluted by broken DWARF, trying another probe"
+
+ # confirm that the broken DWARF comes from assembler
+ if [ -n "$VMLINUX_FILE" ]; then
+ readelf -wi "$VMLINUX_FILE" |
+ awk -v probe="$BLACKFUNC" '/DW_AT_language/ { comp_lang = $0 }
+ $0 ~ probe { if (comp_lang) { print comp_lang }; exit }' |
+ grep -q "MIPS assembler"
+
+ CHECK_EXIT_CODE=$?
+ if [ $CHECK_EXIT_CODE -ne 0 ]; then
+ SKIP_DWARF=0 # broken DWARF while available
+ break
+ fi
+ fi
+ else
+ "$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "$REGEX_SKIP_MESSAGE" "$REGEX_NOT_FOUND_MESSAGE" \
+ "$REGEX_ERROR_MESSAGE" "$REGEX_SCOPE_FAIL" \
+ "$REGEX_INVALID_ARGUMENT" "$REGEX_SYMBOL_FAIL" \
+ "$REGEX_OUT_SECTION" < $LOGS_DIR/adding_blacklisted.err
+ CHECK_EXIT_CODE=$?
+
+ SKIP_DWARF=0
+ break
+ fi
+done
+
+if [ $SKIP_DWARF -eq 1 ]; then
+ print_testcase_skipped "adding blacklisted function $BLACKFUNC"
+else
+ print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "adding blacklisted function $BLACKFUNC"
+ (( TEST_RESULT += $? ))
+fi
+
+### listing not-added probe
+
+# blacklisted probes should NOT appear in perf-list output
+$CMD_PERF list probe:\* > $LOGS_DIR/adding_blacklisted_list.log
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "$RE_LINE_EMPTY" "List of pre-defined events" "Metric Groups:" \
+ < $LOGS_DIR/adding_blacklisted_list.log
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "listing blacklisted probe (should NOT be listed)"
+(( TEST_RESULT += $? ))
+
+
+# print overall results
+print_overall_results "$TEST_RESULT"
+exit $?
diff --git a/tools/perf/tests/shell/base_probe/test_adding_kernel.sh b/tools/perf/tests/shell/base_probe/test_adding_kernel.sh
new file mode 100755
index 000000000..555a825d5
--- /dev/null
+++ b/tools/perf/tests/shell/base_probe/test_adding_kernel.sh
@@ -0,0 +1,346 @@
+#!/bin/bash
+# perf_probe :: Add probes, list and remove them (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+#
+# test_adding_kernel of perf_probe test
+# Author: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
+# Author: Michael Petlan <mpetlan@redhat.com>
+#
+# Description:
+#
+# This test tests adding of probes, their correct listing
+# and removing.
+#
+
+DIR_PATH="$(dirname $0)"
+TEST_RESULT=0
+
+# include working environment
+. "$DIR_PATH/../common/init.sh"
+
+# shellcheck source=lib/probe_vfs_getname.sh
+. "$DIR_PATH/../lib/probe_vfs_getname.sh"
+
+TEST_PROBE=${TEST_PROBE:-"inode_permission"}
+
+# set NO_DEBUGINFO to skip testcase if debuginfo is not present
+# skip_if_no_debuginfo returns 2 if debuginfo is not present
+skip_if_no_debuginfo
+if [ $? -eq 2 ]; then
+ NO_DEBUGINFO=1
+fi
+
+check_kprobes_available
+if [ $? -ne 0 ]; then
+ print_overall_skipped
+ exit 2
+fi
+
+
+### basic probe adding
+
+for opt in "" "-a" "--add"; do
+ clear_all_probes
+ $CMD_PERF probe $opt $TEST_PROBE 2> $LOGS_DIR/adding_kernel_add$opt.err
+ PERF_EXIT_CODE=$?
+
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "Added new events?:" "probe:$TEST_PROBE" "on $TEST_PROBE" \
+ < $LOGS_DIR/adding_kernel_add$opt.err
+ CHECK_EXIT_CODE=$?
+
+ print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "adding probe $TEST_PROBE :: $opt"
+ (( TEST_RESULT += $? ))
+done
+
+
+### listing added probe :: perf list
+
+# any added probes should appear in perf-list output
+$CMD_PERF list probe:\* > $LOGS_DIR/adding_kernel_list.log
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "$RE_LINE_EMPTY" "List of pre-defined events" \
+ "probe:${TEST_PROBE}(?:_\d+)?\s+\[Tracepoint event\]" \
+ "Metric Groups:" < $LOGS_DIR/adding_kernel_list.log
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "listing added probe :: perf list"
+(( TEST_RESULT += $? ))
+
+
+### listing added probe :: perf probe -l
+
+# '-l' should list all the added probes as well
+$CMD_PERF probe -l > $LOGS_DIR/adding_kernel_list-l.log
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "\s*probe:${TEST_PROBE}(?:_\d+)?\s+\(on ${TEST_PROBE}(?:[:\+]$RE_NUMBER_HEX)?@.+\)" \
+ < $LOGS_DIR/adding_kernel_list-l.log
+CHECK_EXIT_CODE=$?
+
+if [ $NO_DEBUGINFO ] ; then
+ print_testcase_skipped $NO_DEBUGINFO $NO_DEBUGINFO "Skipped due to missing debuginfo"
+else
+ print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "listing added probe :: perf probe -l"
+fi
+
+(( TEST_RESULT += $? ))
+
+
+### using added probe
+
+$CMD_PERF stat -e probe:$TEST_PROBE\* -o $LOGS_DIR/adding_kernel_using_probe.log -- cat /proc/uptime > /dev/null
+PERF_EXIT_CODE=$?
+
+REGEX_STAT_HEADER="\s*Performance counter stats for \'cat /proc/uptime\':"
+REGEX_STAT_VALUES="\s*\d+\s+probe:$TEST_PROBE"
+# the value should be greater than 1
+REGEX_STAT_VALUE_NONZERO="\s*[1-9][0-9]*\s+probe:$TEST_PROBE"
+REGEX_STAT_TIME="\s*$RE_NUMBER\s+seconds (?:time elapsed|user|sys)"
+"$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "$REGEX_STAT_HEADER" "$REGEX_STAT_VALUES" "$REGEX_STAT_TIME" \
+ "$RE_LINE_COMMENT" "$RE_LINE_EMPTY" < $LOGS_DIR/adding_kernel_using_probe.log
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "$REGEX_STAT_HEADER" "$REGEX_STAT_VALUE_NONZERO" "$REGEX_STAT_TIME" \
+ < $LOGS_DIR/adding_kernel_using_probe.log
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "using added probe"
+(( TEST_RESULT += $? ))
+
+
+### removing added probe
+
+# '-d' should remove the probe
+$CMD_PERF probe -d $TEST_PROBE\* 2> $LOGS_DIR/adding_kernel_removing.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "Removed event: probe:$TEST_PROBE" < $LOGS_DIR/adding_kernel_removing.err
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "deleting added probe"
+(( TEST_RESULT += $? ))
+
+
+### listing removed probe
+
+# removed probes should NOT appear in perf-list output
+$CMD_PERF list probe:\* > $LOGS_DIR/adding_kernel_list_removed.log
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "$RE_LINE_EMPTY" "List of pre-defined events" "Metric Groups:" \
+ < $LOGS_DIR/adding_kernel_list_removed.log
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "listing removed probe (should NOT be listed)"
+(( TEST_RESULT += $? ))
+
+
+### dry run
+
+# the '-n' switch should run it in dry mode
+$CMD_PERF probe -n --add $TEST_PROBE 2> $LOGS_DIR/adding_kernel_dryrun.err
+PERF_EXIT_CODE=$?
+
+# check for the output (should be the same as usual)
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "Added new events?:" "probe:$TEST_PROBE" "on $TEST_PROBE" \
+ < $LOGS_DIR/adding_kernel_dryrun.err
+CHECK_EXIT_CODE=$?
+
+# check that no probe was added in real
+! ( $CMD_PERF probe -l | grep "probe:$TEST_PROBE" )
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "dry run :: adding probe"
+(( TEST_RESULT += $? ))
+
+
+### force-adding probes
+
+# when using '--force' a probe should be added even if it is already there
+$CMD_PERF probe --add $TEST_PROBE 2> $LOGS_DIR/adding_kernel_forceadd_01.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "Added new events?:" "probe:$TEST_PROBE" "on $TEST_PROBE" \
+ < $LOGS_DIR/adding_kernel_forceadd_01.err
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "force-adding probes :: first probe adding"
+(( TEST_RESULT += $? ))
+
+# adding existing probe without '--force' should fail
+! $CMD_PERF probe --add $TEST_PROBE 2> $LOGS_DIR/adding_kernel_forceadd_02.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "Error: event \"$TEST_PROBE\" already exists." \
+ "Error: Failed to add events." < $LOGS_DIR/adding_kernel_forceadd_02.err
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "force-adding probes :: second probe adding (without force)"
+(( TEST_RESULT += $? ))
+
+# adding existing probe with '--force' should pass
+NO_OF_PROBES=`$CMD_PERF probe -l $TEST_PROBE| wc -l`
+$CMD_PERF probe --force --add $TEST_PROBE 2> $LOGS_DIR/adding_kernel_forceadd_03.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "Added new events?:" "probe:${TEST_PROBE}_${NO_OF_PROBES}" \
+ "on $TEST_PROBE" < $LOGS_DIR/adding_kernel_forceadd_03.err
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "force-adding probes :: second probe adding (with force)"
+(( TEST_RESULT += $? ))
+
+
+### using doubled probe
+
+# since they are the same, they should produce the same results
+$CMD_PERF stat -e probe:$TEST_PROBE -e probe:${TEST_PROBE}_${NO_OF_PROBES} -x';' -o $LOGS_DIR/adding_kernel_using_two.log -- bash -c 'cat /proc/cpuinfo > /dev/null'
+PERF_EXIT_CODE=$?
+
+REGEX_LINE="$RE_NUMBER;+probe:${TEST_PROBE}_?(?:$NO_OF_PROBES)?;$RE_NUMBER;$RE_NUMBER"
+"$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "$REGEX_LINE" "$RE_LINE_EMPTY" "$RE_LINE_COMMENT" \
+ < $LOGS_DIR/adding_kernel_using_two.log
+CHECK_EXIT_CODE=$?
+
+VALUE_1=`grep "$TEST_PROBE;" $LOGS_DIR/adding_kernel_using_two.log | awk -F';' '{print $1}'`
+VALUE_2=`grep "${TEST_PROBE}_${NO_OF_PROBES};" $LOGS_DIR/adding_kernel_using_two.log | awk -F';' '{print $1}'`
+
+test $VALUE_1 -eq $VALUE_2
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "using doubled probe"
+
+
+### removing multiple probes
+
+# using wildcards should remove all matching probes
+$CMD_PERF probe --del \* 2> $LOGS_DIR/adding_kernel_removing_wildcard.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "Removed event: probe:$TEST_PROBE" \
+ "Removed event: probe:${TEST_PROBE}_1" < $LOGS_DIR/adding_kernel_removing_wildcard.err
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "removing multiple probes"
+(( TEST_RESULT += $? ))
+
+
+### wildcard adding support
+
+$CMD_PERF probe -nf --max-probes=512 -a 'vfs_* $params' 2> $LOGS_DIR/adding_kernel_adding_wildcard.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "probe:vfs_mknod" "probe:vfs_create" "probe:vfs_rmdir" \
+ "probe:vfs_link" "probe:vfs_write" < $LOGS_DIR/adding_kernel_adding_wildcard.err
+CHECK_EXIT_CODE=$?
+
+if [ $NO_DEBUGINFO ] ; then
+ print_testcase_skipped $NO_DEBUGINFO $NO_DEBUGINFO "Skipped due to missing debuginfo"
+else
+ print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "wildcard adding support"
+fi
+
+(( TEST_RESULT += $? ))
+
+
+### non-existing variable
+
+# perf probe should survive a non-existing variable probing attempt
+{ $CMD_PERF probe 'vfs_read somenonexistingrandomstuffwhichisalsoprettylongorevenlongertoexceed64' ; } 2> $LOGS_DIR/adding_kernel_nonexisting.err
+PERF_EXIT_CODE=$?
+
+# the exitcode should not be 0 or segfault
+test $PERF_EXIT_CODE -ne 139 -a $PERF_EXIT_CODE -ne 0
+PERF_EXIT_CODE=$?
+
+# check that the error message is reasonable
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "Failed to find" \
+ "somenonexistingrandomstuffwhichisalsoprettylongorevenlongertoexceed64" \
+ < $LOGS_DIR/adding_kernel_nonexisting.err
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "in this function|at this address" "Error" "Failed to add events" \
+ < $LOGS_DIR/adding_kernel_nonexisting.err
+(( CHECK_EXIT_CODE += $? ))
+"$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "Failed to find" "Error" "Probe point .+ not found" "optimized out" \
+ "Use.+\-\-range option to show.+location range" \
+ < $LOGS_DIR/adding_kernel_nonexisting.err
+(( CHECK_EXIT_CODE += $? ))
+"$DIR_PATH/../common/check_no_patterns_found.pl" \
+ "$RE_SEGFAULT" < $LOGS_DIR/adding_kernel_nonexisting.err
+(( CHECK_EXIT_CODE += $? ))
+
+if [ $NO_DEBUGINFO ]; then
+ print_testcase_skipped $NO_DEBUGINFO $NO_DEBUGINFO "Skipped due to missing debuginfo"
+else
+ print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "non-existing variable"
+fi
+
+(( TEST_RESULT += $? ))
+
+
+### function with return value
+
+# adding probe with return value
+$CMD_PERF probe --add "$TEST_PROBE%return \$retval" 2> $LOGS_DIR/adding_kernel_func_retval_add.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "Added new events?:" "probe:$TEST_PROBE" \
+ "on $TEST_PROBE%return with \\\$retval" \
+ < $LOGS_DIR/adding_kernel_func_retval_add.err
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "function with retval :: add"
+(( TEST_RESULT += $? ))
+
+# recording some data
+$CMD_PERF record -e probe:$TEST_PROBE\* -o $CURRENT_TEST_DIR/perf.data -- cat /proc/cpuinfo > /dev/null 2> $LOGS_DIR/adding_kernel_func_retval_record.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "$RE_LINE_RECORD1" "$RE_LINE_RECORD2" \
+ < $LOGS_DIR/adding_kernel_func_retval_record.err
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "function with retval :: record"
+(( TEST_RESULT += $? ))
+
+# perf script should report the function calls with the correct arg values
+$CMD_PERF script -i $CURRENT_TEST_DIR/perf.data > $LOGS_DIR/adding_kernel_func_retval_script.log
+PERF_EXIT_CODE=$?
+
+REGEX_SCRIPT_LINE="\s*cat\s+$RE_NUMBER\s+\[$RE_NUMBER\]\s+$RE_NUMBER:\s+probe:$TEST_PROBE\w*:\s+\($RE_NUMBER_HEX\s+<\-\s+$RE_NUMBER_HEX\)\s+arg1=$RE_NUMBER_HEX"
+"$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "$REGEX_SCRIPT_LINE" < $LOGS_DIR/adding_kernel_func_retval_script.log
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "$REGEX_SCRIPT_LINE" < $LOGS_DIR/adding_kernel_func_retval_script.log
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "function argument probing :: script"
+(( TEST_RESULT += $? ))
+
+
+clear_all_probes
+
+# print overall results
+print_overall_results "$TEST_RESULT"
+exit $?
diff --git a/tools/perf/tests/shell/base_probe/test_basic.sh b/tools/perf/tests/shell/base_probe/test_basic.sh
new file mode 100755
index 000000000..162838ddc
--- /dev/null
+++ b/tools/perf/tests/shell/base_probe/test_basic.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+# perf_probe :: Basic perf probe functionality (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+#
+# test_basic of perf_probe test
+# Author: Michael Petlan <mpetlan@redhat.com>
+# Author: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
+#
+# Description:
+#
+# This test tests basic functionality of perf probe command.
+#
+
+DIR_PATH="$(dirname $0)"
+TEST_RESULT=0
+
+# include working environment
+. "$DIR_PATH/../common/init.sh"
+
+if ! check_kprobes_available; then
+ print_overall_skipped
+ exit 2
+fi
+
+
+### help message
+
+if [ "$PARAM_GENERAL_HELP_TEXT_CHECK" = "y" ]; then
+ # test that a help message is shown and looks reasonable
+ $CMD_PERF probe --help > $LOGS_DIR/basic_helpmsg.log 2> $LOGS_DIR/basic_helpmsg.err
+ PERF_EXIT_CODE=$?
+
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "PERF-PROBE" "NAME" "SYNOPSIS" "DESCRIPTION" "OPTIONS" \
+ "PROBE\s+SYNTAX" "PROBE\s+ARGUMENT" "LINE\s+SYNTAX" \
+ < $LOGS_DIR/basic_helpmsg.log
+ CHECK_EXIT_CODE=$?
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "LAZY\s+MATCHING" "FILTER\s+PATTERN" "EXAMPLES" "SEE\s+ALSO" \
+ < $LOGS_DIR/basic_helpmsg.log
+ (( CHECK_EXIT_CODE += $? ))
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "vmlinux" "module=" "source=" "verbose" "quiet" "add=" "del=" \
+ "list.*EVENT" "line=" "vars=" "externs" < $LOGS_DIR/basic_helpmsg.log
+ (( CHECK_EXIT_CODE += $? ))
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "no-inlines" "funcs.*FILTER" "filter=FILTER" "force" "dry-run" \
+ "max-probes" "exec=" "demangle-kernel" < $LOGS_DIR/basic_helpmsg.log
+ (( CHECK_EXIT_CODE += $? ))
+ "$DIR_PATH/../common/check_no_patterns_found.pl" \
+ "No manual entry for" < $LOGS_DIR/basic_helpmsg.err
+ (( CHECK_EXIT_CODE += $? ))
+
+ print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "help message"
+ (( TEST_RESULT += $? ))
+else
+ print_testcase_skipped "help message"
+fi
+
+
+### usage message
+
+# without any args perf-probe should print usage
+$CMD_PERF probe 2> $LOGS_DIR/basic_usage.log > /dev/null
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "[Uu]sage" "perf probe" "verbose" "quiet" "add" "del" "force" \
+ "line" "vars" "externs" "range" < $LOGS_DIR/basic_usage.log
+CHECK_EXIT_CODE=$?
+
+print_results 0 $CHECK_EXIT_CODE "usage message"
+(( TEST_RESULT += $? ))
+
+
+### quiet switch
+
+# '--quiet' should mute all output
+$CMD_PERF probe --quiet --add vfs_read > $LOGS_DIR/basic_quiet01.log 2> $LOGS_DIR/basic_quiet01.err
+PERF_EXIT_CODE=$?
+$CMD_PERF probe --quiet --del vfs_read > $LOGS_DIR/basic_quiet03.log 2> $LOGS_DIR/basic_quiet02.err
+(( PERF_EXIT_CODE += $? ))
+
+test "`cat $LOGS_DIR/basic_quiet*log $LOGS_DIR/basic_quiet*err | wc -l`" -eq 0
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "quiet switch"
+(( TEST_RESULT += $? ))
+
+
+# print overall results
+print_overall_results "$TEST_RESULT"
+exit $?
diff --git a/tools/perf/tests/shell/base_probe/test_invalid_options.sh b/tools/perf/tests/shell/base_probe/test_invalid_options.sh
new file mode 100755
index 000000000..44a3ae014
--- /dev/null
+++ b/tools/perf/tests/shell/base_probe/test_invalid_options.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+# perf_probe :: Reject invalid options (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+#
+# test_invalid_options of perf_probe test
+# Author: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
+# Author: Michael Petlan <mpetlan@redhat.com>
+#
+# Description:
+#
+# This test checks whether the invalid and incompatible options are reported
+#
+
+DIR_PATH="$(dirname $0)"
+TEST_RESULT=0
+
+# include working environment
+. "$DIR_PATH/../common/init.sh"
+
+if ! check_kprobes_available; then
+ print_overall_skipped
+ exit 2
+fi
+
+# Check for presence of DWARF
+$CMD_PERF check feature -q dwarf
+[ $? -ne 0 ] && HINT_FAIL="Some of the tests need DWARF to run"
+
+### missing argument
+
+# some options require an argument
+for opt in '-a' '-d' '-L' '-V'; do
+ ! $CMD_PERF probe $opt 2> $LOGS_DIR/invalid_options_missing_argument$opt.err
+ PERF_EXIT_CODE=$?
+
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "Error: switch .* requires a value" \
+ < $LOGS_DIR/invalid_options_missing_argument$opt.err
+ CHECK_EXIT_CODE=$?
+
+ print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "missing argument for $opt"
+ (( TEST_RESULT += $? ))
+done
+
+
+### unnecessary argument
+
+# some options may omit the argument
+for opt in '-F' '-l'; do
+ $CMD_PERF probe -F > /dev/null 2> $LOGS_DIR/invalid_options_unnecessary_argument$opt.err
+ PERF_EXIT_CODE=$?
+
+ test ! -s $LOGS_DIR/invalid_options_unnecessary_argument$opt.err
+ CHECK_EXIT_CODE=$?
+
+ print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "unnecessary argument for $opt"
+ (( TEST_RESULT += $? ))
+done
+
+
+### mutually exclusive options
+
+# some options are mutually exclusive
+test -e $LOGS_DIR/invalid_options_mutually_exclusive.log && rm -f $LOGS_DIR/invalid_options_mutually_exclusive.log
+for opt in '-a xxx -d xxx' '-a xxx -L foo' '-a xxx -V foo' '-a xxx -l' '-a xxx -F' \
+ '-d xxx -L foo' '-d xxx -V foo' '-d xxx -l' '-d xxx -F' \
+ '-L foo -V bar' '-L foo -l' '-L foo -F' '-V foo -l' '-V foo -F' '-l -F'; do
+ ! $CMD_PERF probe $opt > /dev/null 2> $LOGS_DIR/aux.log
+ PERF_EXIT_CODE=$?
+
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "Error: switch .+ cannot be used with switch .+" < $LOGS_DIR/aux.log
+ CHECK_EXIT_CODE=$?
+
+ print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "mutually exclusive options :: $opt"
+ (( TEST_RESULT += $? ))
+
+ # gather the logs
+ cat $LOGS_DIR/aux.log | grep "Error" >> $LOGS_DIR/invalid_options_mutually_exclusive.log
+done
+
+
+# print overall results
+print_overall_results "$TEST_RESULT" $HINT_FAIL
+exit $?
diff --git a/tools/perf/tests/shell/base_probe/test_line_semantics.sh b/tools/perf/tests/shell/base_probe/test_line_semantics.sh
new file mode 100755
index 000000000..576442d87
--- /dev/null
+++ b/tools/perf/tests/shell/base_probe/test_line_semantics.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+# perf_probe :: Check patterns for line semantics (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+#
+# test_line_semantics of perf_probe test
+# Author: Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
+# Author: Michael Petlan <mpetlan@redhat.com>
+#
+# Description:
+#
+# This test checks whether the semantic errors of line option's
+# arguments are properly reported.
+#
+
+DIR_PATH="$(dirname $0)"
+TEST_RESULT=0
+
+# include working environment
+. "$DIR_PATH/../common/init.sh"
+
+if ! check_kprobes_available; then
+ print_overall_skipped
+ exit 2
+fi
+
+# Check for presence of DWARF
+$CMD_PERF check feature -q dwarf
+[ $? -ne 0 ] && HINT_FAIL="Some of the tests need DWARF to run"
+
+### acceptable --line descriptions
+
+# testing acceptance of valid patterns for the '--line' option
+VALID_PATTERNS="func func:10 func:0-10 func:2+10 func@source.c func@source.c:1 source.c:1 source.c:1+1 source.c:1-10"
+for desc in $VALID_PATTERNS; do
+ ! ( $CMD_PERF probe --line $desc 2>&1 | grep -q "Semantic error" )
+ CHECK_EXIT_CODE=$?
+
+ print_results 0 $CHECK_EXIT_CODE "acceptable descriptions :: $desc"
+ (( TEST_RESULT += $? ))
+done
+
+
+### unacceptable --line descriptions
+
+# testing handling of invalid patterns for the '--line' option
+INVALID_PATTERNS="func:foo func:1-foo func:1+foo func;lazy\*pattern"
+for desc in $INVALID_PATTERNS; do
+ $CMD_PERF probe --line $desc 2>&1 | grep -q "Semantic error"
+ CHECK_EXIT_CODE=$?
+
+ print_results 0 $CHECK_EXIT_CODE "unacceptable descriptions :: $desc"
+ (( TEST_RESULT += $? ))
+done
+
+
+# print overall results
+print_overall_results "$TEST_RESULT" $HINT_FAIL
+exit $?
diff --git a/tools/perf/tests/shell/base_report/setup.sh b/tools/perf/tests/shell/base_report/setup.sh
new file mode 100755
index 000000000..bb49b0fab
--- /dev/null
+++ b/tools/perf/tests/shell/base_report/setup.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# perftool-testsuite :: perf_report
+# SPDX-License-Identifier: GPL-2.0
+
+#
+# setup.sh of perf report test
+# Author: Michael Petlan <mpetlan@redhat.com>
+#
+# Description:
+#
+# We need some sample data for perf-report testing
+#
+#
+
+DIR_PATH="$(dirname $0)"
+
+# include working environment
+. "$DIR_PATH/../common/init.sh"
+
+TEST_RESULT=0
+
+test -d "$HEADER_TAR_DIR" || mkdir -p "$HEADER_TAR_DIR"
+
+SW_EVENT="cpu-clock"
+
+$CMD_PERF record -asdg -e $SW_EVENT -o $CURRENT_TEST_DIR/perf.data -- $CMD_LONGER_SLEEP 2> $LOGS_DIR/setup.log
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "$RE_LINE_RECORD1" "$RE_LINE_RECORD2" < $LOGS_DIR/setup.log
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "prepare the perf.data file"
+(( TEST_RESULT += $? ))
+
+# Some minimal parallel workload.
+$CMD_PERF record --latency -o $CURRENT_TEST_DIR/perf.data.1 bash -c "for i in {1..100} ; do cat /proc/cpuinfo 1> /dev/null & done; sleep 1" 2> $LOGS_DIR/setup-latency.log
+PERF_EXIT_CODE=$?
+
+echo ==================
+cat $LOGS_DIR/setup-latency.log
+echo ==================
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "$RE_LINE_RECORD1" "$RE_LINE_RECORD2" < $LOGS_DIR/setup-latency.log
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "prepare the perf.data.1 file"
+(( TEST_RESULT += $? ))
+
+print_overall_results $TEST_RESULT
+exit $?
diff --git a/tools/perf/tests/shell/base_report/stderr-whitelist.txt b/tools/perf/tests/shell/base_report/stderr-whitelist.txt
new file mode 100644
index 000000000..e3341401b
--- /dev/null
+++ b/tools/perf/tests/shell/base_report/stderr-whitelist.txt
@@ -0,0 +1,5 @@
+no symbols found in .*, maybe install a debug package
+was updated .*is prelink enabled.+ Restart the long running apps that use it
+Warning:
+\d+ out of order events recorded.
+detected invalid bpf_prog_info
diff --git a/tools/perf/tests/shell/base_report/test_basic.sh b/tools/perf/tests/shell/base_report/test_basic.sh
new file mode 100755
index 000000000..0dfe7e5fd
--- /dev/null
+++ b/tools/perf/tests/shell/base_report/test_basic.sh
@@ -0,0 +1,285 @@
+#!/bin/bash
+# perf_report :: Basic perf report options (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+#
+# test_basic of perf_report test
+# Author: Michael Petlan <mpetlan@redhat.com>
+#
+# Description:
+#
+# This test tests basic functionality of perf report command.
+#
+#
+
+DIR_PATH="$(dirname $0)"
+TEST_RESULT=0
+
+# include working environment
+. "$DIR_PATH/../common/init.sh"
+
+
+### help message
+
+if [ "$PARAM_GENERAL_HELP_TEXT_CHECK" = "y" ]; then
+ # test that a help message is shown and looks reasonable
+ $CMD_PERF report --help > $LOGS_DIR/basic_helpmsg.log 2> $LOGS_DIR/basic_helpmsg.err
+ PERF_EXIT_CODE=$?
+
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "PERF-REPORT" "NAME" "SYNOPSIS" "DESCRIPTION" "OPTIONS" \
+ "OVERHEAD\s+CALCULATION" "SEE ALSO" < $LOGS_DIR/basic_helpmsg.log
+ CHECK_EXIT_CODE=$?
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "input" "verbose" "show-nr-samples" "show-cpu-utilization" \
+ "threads" "comms" "pid" "tid" "dsos" "symbols" "symbol-filter" \
+ < $LOGS_DIR/basic_helpmsg.log
+ (( CHECK_EXIT_CODE += $? ))
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "hide-unresolved" "sort" "fields" "parent" "exclude-other" \
+ "column-widths" "field-separator" "dump-raw-trace" "children" \
+ < $LOGS_DIR/basic_helpmsg.log
+ (( CHECK_EXIT_CODE += $? ))
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "call-graph" "max-stack" "inverted" "ignore-callees" "pretty" \
+ "stdio" "tui" "gtk" "vmlinux" "kallsyms" "modules" \
+ < $LOGS_DIR/basic_helpmsg.log
+ (( CHECK_EXIT_CODE += $? ))
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "force" "symfs" "cpu" "disassembler-style" "source" "asm-raw" \
+ "show-total-period" "show-info" "branch-stack" "group" \
+ < $LOGS_DIR/basic_helpmsg.log
+ (( CHECK_EXIT_CODE += $? ))
+ "$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "branch-history" "objdump" "demangle" "percent-limit" "percentage" \
+ "header" "itrace" "full-source-path" "show-ref-call-graph" \
+ < $LOGS_DIR/basic_helpmsg.log
+ (( CHECK_EXIT_CODE += $? ))
+ "$DIR_PATH/../common/check_no_patterns_found.pl" \
+ "No manual entry for" < $LOGS_DIR/basic_helpmsg.err
+ (( CHECK_EXIT_CODE += $? ))
+
+ print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "help message"
+ (( TEST_RESULT += $? ))
+else
+ print_testcase_skipped "help message"
+fi
+
+
+### basic execution
+
+# test that perf report is even working
+$CMD_PERF report -i $CURRENT_TEST_DIR/perf.data --stdio > $LOGS_DIR/basic_basic.log 2> $LOGS_DIR/basic_basic.err
+PERF_EXIT_CODE=$?
+
+REGEX_LOST_SAMPLES_INFO="#\s*Total Lost Samples:\s+$RE_NUMBER"
+REGEX_SAMPLES_INFO="#\s*Samples:\s+(?:$RE_NUMBER)\w?\s+of\s+event\s+'$RE_EVENT_ANY'"
+REGEX_LINES_HEADER="#\s*Children\s+Self\s+Command\s+Shared Object\s+Symbol"
+REGEX_LINES="\s*$RE_NUMBER%\s+$RE_NUMBER%\s+\S+\s+\[kernel\.(?:vmlinux)|(?:kallsyms)\]\s+\[[k\.]\]\s+\w+"
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "$REGEX_LOST_SAMPLES_INFO" "$REGEX_SAMPLES_INFO" \
+ "$REGEX_LINES_HEADER" "$REGEX_LINES" < $LOGS_DIR/basic_basic.log
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_errors_whitelisted.pl" \
+ "$DIR_PATH/stderr-whitelist.txt" < $LOGS_DIR/basic_basic.err
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "basic execution"
+(( TEST_RESULT += $? ))
+
+
+### number of samples
+
+# '--show-nr-samples' should show number of samples for each symbol
+$CMD_PERF report --stdio -i $CURRENT_TEST_DIR/perf.data --show-nr-samples > $LOGS_DIR/basic_nrsamples.log 2> $LOGS_DIR/basic_nrsamples.err
+PERF_EXIT_CODE=$?
+
+REGEX_LINES_HEADER="#\s*Children\s+Self\s+Samples\s+Command\s+Shared Object\s+Symbol"
+REGEX_LINES="\s*$RE_NUMBER%\s+$RE_NUMBER%\s+$RE_NUMBER\s+\S+\s+\[kernel\.(?:vmlinux)|(?:kallsyms)\]\s+\[[k\.]\]\s+\w+"
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "$REGEX_LINES_HEADER" "$REGEX_LINES" < $LOGS_DIR/basic_nrsamples.log
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_errors_whitelisted.pl" \
+ "$DIR_PATH/stderr-whitelist.txt" < $LOGS_DIR/basic_nrsamples.err
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "number of samples"
+(( TEST_RESULT += $? ))
+
+
+### header
+
+# '--header' and '--header-only' should show perf report header
+$CMD_PERF report -i $CURRENT_TEST_DIR/perf.data --stdio --header-only > $LOGS_DIR/basic_header.log
+PERF_EXIT_CODE=$?
+
+REGEX_LINE_TIMESTAMP="#\s+captured on\s*:\s*$RE_DATE_TIME"
+REGEX_LINE_HOSTNAME="#\s+hostname\s*:\s*$MY_HOSTNAME"
+REGEX_LINE_KERNEL="#\s+os release\s*:\s*${MY_KERNEL_VERSION//+/\\+}"
+REGEX_LINE_PERF="#\s+perf version\s*:\s*"
+REGEX_LINE_ARCH="#\s+arch\s*:\s*$MY_ARCH"
+REGEX_LINE_CPUS_ONLINE="#\s+nrcpus online\s*:\s*$MY_CPUS_ONLINE"
+REGEX_LINE_CPUS_AVAIL="#\s+nrcpus avail\s*:\s*$MY_CPUS_AVAILABLE"
+# disable precise check for "nrcpus avail" in BASIC runmode
+test $PERFTOOL_TESTSUITE_RUNMODE -lt $RUNMODE_STANDARD && REGEX_LINE_CPUS_AVAIL="#\s+nrcpus avail\s*:\s*$RE_NUMBER"
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "$REGEX_LINE_TIMESTAMP" "$REGEX_LINE_HOSTNAME" "$REGEX_LINE_KERNEL" \
+ "$REGEX_LINE_PERF" "$REGEX_LINE_ARCH" "$REGEX_LINE_CPUS_ONLINE" \
+ "$REGEX_LINE_CPUS_AVAIL" < $LOGS_DIR/basic_header.log
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "header"
+(( TEST_RESULT += $? ))
+
+# '--header' and '--header-only' should use creation time
+OLD_TIMESTAMP=`$CMD_PERF report --stdio --header-only -i $CURRENT_TEST_DIR/perf.data | grep "captured on"`
+PERF_EXIT_CODE=$?
+
+( tar -C $CURRENT_TEST_DIR -c perf.data | xz > $CURRENT_TEST_DIR/perf.data.tar.xz ; xzcat $CURRENT_TEST_DIR/perf.data.tar.xz | tar x -C $HEADER_TAR_DIR )
+(( PERF_EXIT_CODE += $? ))
+
+NEW_TIMESTAMP=`$CMD_PERF report --stdio --header-only -i $HEADER_TAR_DIR/perf.data | grep "captured on"`
+(( PERF_EXIT_CODE += $? ))
+
+test "$OLD_TIMESTAMP" = "$NEW_TIMESTAMP"
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "header timestamp"
+(( TEST_RESULT += $? ))
+
+
+### show CPU utilization
+
+# '--showcpuutilization' should show percentage for both system and userspace mode
+$CMD_PERF report -i $CURRENT_TEST_DIR/perf.data --stdio --showcpuutilization > $LOGS_DIR/basic_cpuut.log 2> $LOGS_DIR/basic_cpuut.err
+PERF_EXIT_CODE=$?
+
+REGEX_LINES_HEADER="#\s*Children\s+Self\s+sys\s+usr\s+Command\s+Shared Object\s+Symbol"
+REGEX_LINES="\s*$RE_NUMBER%\s+$RE_NUMBER%\s+$RE_NUMBER%\s+$RE_NUMBER%\s+\S+\s+\[kernel\.(?:vmlinux)|(?:kallsyms)\]\s+\[[k\.]\]\s+\w+"
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "$REGEX_LINES_HEADER" "$REGEX_LINES" < $LOGS_DIR/basic_cpuut.log
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_errors_whitelisted.pl" \
+ "$DIR_PATH/stderr-whitelist.txt" < $LOGS_DIR/basic_cpuut.err
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "show CPU utilization"
+(( TEST_RESULT += $? ))
+
+
+### pid
+
+# '--pid=' should limit the output for a process with the given pid only
+$CMD_PERF report --stdio -i $CURRENT_TEST_DIR/perf.data --pid=1 > $LOGS_DIR/basic_pid.log 2> $LOGS_DIR/basic_pid.err
+PERF_EXIT_CODE=$?
+
+grep -P -v '^#' $LOGS_DIR/basic_pid.log | grep -P '\s+[\d\.]+%' | \
+ "$DIR_PATH/../common/check_all_lines_matched.pl" "systemd|init"
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_errors_whitelisted.pl" \
+ "$DIR_PATH/stderr-whitelist.txt" < $LOGS_DIR/basic_pid.err
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "pid"
+(( TEST_RESULT += $? ))
+
+
+### non-existing symbol
+
+# '--symbols' should show only the given symbols
+$CMD_PERF report --stdio -i $CURRENT_TEST_DIR/perf.data --symbols=dummynonexistingsymbol > $LOGS_DIR/basic_symbols.log 2> $LOGS_DIR/basic_symbols.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_lines_matched.pl" \
+ "$RE_LINE_EMPTY" "$RE_LINE_COMMENT" < $LOGS_DIR/basic_symbols.log
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_errors_whitelisted.pl" \
+ "$DIR_PATH/stderr-whitelist.txt" < $LOGS_DIR/basic_symbols.err
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "non-existing symbol"
+(( TEST_RESULT += $? ))
+
+
+### symbol filter
+
+# '--symbol-filter' should filter symbols based on substrings
+$CMD_PERF report --stdio -i $CURRENT_TEST_DIR/perf.data --symbol-filter=map > $LOGS_DIR/basic_symbolfilter.log 2> $LOGS_DIR/basic_symbolfilter.err
+PERF_EXIT_CODE=$?
+
+grep -P -v '^#' $LOGS_DIR/basic_symbolfilter.log | grep -P '\s+[\d\.]+%' | \
+ "$DIR_PATH/../common/check_all_lines_matched.pl" "\[[k\.]\]\s+.*map"
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_errors_whitelisted.pl" \
+ "$DIR_PATH/stderr-whitelist.txt" < $LOGS_DIR/basic_symbolfilter.err
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "symbol filter"
+(( TEST_RESULT += $? ))
+
+
+### latency and parallelism
+
+# Record with --latency should record with context switches.
+$CMD_PERF report -i $CURRENT_TEST_DIR/perf.data.1 --stdio --header-only > $LOGS_DIR/latency_header.log
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ ", context_switch = 1, " < $LOGS_DIR/latency_header.log
+CHECK_EXIT_CODE=$?
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "latency header"
+(( TEST_RESULT += $? ))
+
+
+# The default report for latency profile should show Overhead and Latency fields (in that order).
+$CMD_PERF report --stdio -i $CURRENT_TEST_DIR/perf.data.1 > $LOGS_DIR/latency_default.log 2> $LOGS_DIR/latency_default.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "# Overhead Latency Command" < $LOGS_DIR/latency_default.log
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_errors_whitelisted.pl" \
+ "stderr-whitelist.txt" < $LOGS_DIR/latency_default.err
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "default report for latency profile"
+(( TEST_RESULT += $? ))
+
+
+# The latency report for latency profile should show Latency and Overhead fields (in that order).
+$CMD_PERF report --latency --stdio -i $CURRENT_TEST_DIR/perf.data.1 > $LOGS_DIR/latency_latency.log 2> $LOGS_DIR/latency_latency.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "# Latency Overhead Command" < $LOGS_DIR/latency_latency.log
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_errors_whitelisted.pl" \
+ "stderr-whitelist.txt" < $LOGS_DIR/latency_latency.err
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "latency report for latency profile"
+(( TEST_RESULT += $? ))
+
+
+# Ensure parallelism histogram with parallelism filter does not fail/crash.
+$CMD_PERF report --hierarchy --sort latency,parallelism,comm,symbol --parallelism=1,2 --stdio -i $CURRENT_TEST_DIR/perf.data.1 > $LOGS_DIR/parallelism_hierarchy.log 2> $LOGS_DIR/parallelism_hierarchy.err
+PERF_EXIT_CODE=$?
+
+"$DIR_PATH/../common/check_all_patterns_found.pl" \
+ "# Latency Parallelism / Command / Symbol" \
+ < $LOGS_DIR/parallelism_hierarchy.log
+CHECK_EXIT_CODE=$?
+"$DIR_PATH/../common/check_errors_whitelisted.pl" \
+ "stderr-whitelist.txt" < $LOGS_DIR/parallelism_hierarchy.err
+(( CHECK_EXIT_CODE += $? ))
+
+print_results $PERF_EXIT_CODE $CHECK_EXIT_CODE "parallelism histogram"
+(( TEST_RESULT += $? ))
+
+
+# TODO: $CMD_PERF report -n --showcpuutilization -TUxDg 2> 01.log
+
+# print overall results
+print_overall_results "$TEST_RESULT"
+exit $?
diff --git a/tools/perf/tests/shell/buildid.sh b/tools/perf/tests/shell/buildid.sh
new file mode 100755
index 000000000..102808cca
--- /dev/null
+++ b/tools/perf/tests/shell/buildid.sh
@@ -0,0 +1,326 @@
+#!/bin/bash
+# build id cache operations
+# SPDX-License-Identifier: GPL-2.0
+
+# skip if there's no readelf
+if ! [ -x "$(command -v readelf)" ]; then
+ echo "failed: no readelf, install binutils"
+ exit 2
+fi
+
+# skip if there's no compiler
+if ! [ -x "$(command -v cc)" ]; then
+ echo "failed: no compiler, install gcc"
+ exit 2
+fi
+
+# check what we need to test windows binaries
+add_pe=1
+run_pe=1
+if ! perf version --build-options | grep -q 'libbfd: .* on '; then
+ echo "WARNING: perf not built with libbfd. PE binaries will not be tested."
+ add_pe=0
+ run_pe=0
+fi
+if ! which wine > /dev/null; then
+ echo "WARNING: wine not found. PE binaries will not be run."
+ run_pe=0
+fi
+
+# set up wine
+if [ ${run_pe} -eq 1 ]; then
+ wineprefix=$(mktemp -d /tmp/perf.wineprefix.XXX)
+ export WINEPREFIX=${wineprefix}
+ # clear display variables to prevent wine from popping up dialogs
+ unset DISPLAY
+ unset WAYLAND_DISPLAY
+fi
+
+build_id_dir=
+ex_source=$(mktemp /tmp/perf_buildid_test.ex.XXX.c)
+ex_md5=$(mktemp /tmp/perf_buildid_test.ex.MD5.XXX)
+ex_sha1=$(mktemp /tmp/perf_buildid_test.ex.SHA1.XXX)
+ex_pe=$(dirname $0)/../pe-file.exe
+data=$(mktemp /tmp/perf_buildid_test.data.XXX)
+log_out=$(mktemp /tmp/perf_buildid_test.log.out.XXX)
+log_err=$(mktemp /tmp/perf_buildid_test.log.err.XXX)
+
+cleanup() {
+ rm -f ${ex_source} ${ex_md5} ${ex_sha1} ${data} ${log_out} ${log_err}
+ if [ ${run_pe} -eq 1 ]; then
+ rm -r ${wineprefix}
+ fi
+ if [ -d ${build_id_dir} ]; then
+ rm -rf ${build_id_dir}
+ fi
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+# Test program based on the noploop workload.
+cat <<EOF > ${ex_source}
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+
+static volatile sig_atomic_t done;
+
+static void sighandler(int sig)
+{
+ (void)sig;
+ done = 1;
+}
+
+int main(int argc, const char **argv)
+{
+ int sec = 1;
+
+ if (argc > 1)
+ sec = atoi(argv[1]);
+
+ signal(SIGINT, sighandler);
+ signal(SIGALRM, sighandler);
+ alarm(sec);
+
+ while (!done)
+ continue;
+
+ return 0;
+}
+EOF
+cc -Wl,--build-id=sha1 ${ex_source} -o ${ex_sha1} -x c -
+cc -Wl,--build-id=md5 ${ex_source} -o ${ex_md5} -x c -
+echo "test binaries: ${ex_sha1} ${ex_md5} ${ex_pe}"
+
+get_build_id()
+{
+ case $1 in
+ *.exe)
+ # We don't have a tool that can pull a nicely formatted build-id out of
+ # a PE file, but we can extract the whole section with objcopy and
+ # format it ourselves. The .buildid section is a Debug Directory
+ # containing a CodeView entry:
+ # https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#debug-directory-image-only
+ # https://github.com/dotnet/runtime/blob/da94c022576a5c3bbc0e896f006565905eb137f9/docs/design/specs/PE-COFF.md
+ # The build-id starts at byte 33 and must be rearranged into a GUID.
+ id=`objcopy -O binary --only-section=.buildid $1 /dev/stdout | \
+ cut -c 33-48 | hexdump -ve '/1 "%02x"' | \
+ sed 's@^\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(..\)\(.*\)0a$@\4\3\2\1\6\5\8\7\9@'`
+ ;;
+ *)
+ id=`readelf -n ${1} 2>/dev/null | grep 'Build ID' | awk '{print $3}'`
+ ;;
+ esac
+ echo ${id}
+}
+
+check()
+{
+ file=$1
+ perf_data=$2
+
+ id=$(get_build_id $file)
+ echo "build id: ${id}"
+
+ id_file=${id#??}
+ id_dir=${id%$id_file}
+ link=$build_id_dir/.build-id/$id_dir/$id_file
+ echo "link: ${link}"
+
+ if [ ! -h $link ]; then
+ echo "failed: link ${link} does not exist"
+ exit 1
+ fi
+
+ cached_file=${build_id_dir}/.build-id/$id_dir/`readlink ${link}`/elf
+ echo "file: ${cached_file}"
+
+ # Check for file permission of original file
+ # in case of pe-file.exe file
+ echo $1 | grep ".exe"
+ if [ $? -eq 0 ]; then
+ if [ -x $1 ] && [ ! -x $cached_file ]; then
+ echo "failed: file ${cached_file} executable does not exist"
+ exit 1
+ fi
+
+ if [ ! -x $cached_file ] && [ ! -e $cached_file ]; then
+ echo "failed: file ${cached_file} does not exist"
+ exit 1
+ fi
+ elif [ ! -x $cached_file ]; then
+ echo "failed: file ${cached_file} does not exist"
+ exit 1
+ fi
+
+ diff ${cached_file} ${1}
+ if [ $? -ne 0 ]; then
+ echo "failed: ${cached_file} do not match"
+ exit 1
+ fi
+
+ ${perf} buildid-cache -l | grep -q ${id}
+ if [ $? -ne 0 ]; then
+ echo "failed: ${id} is not reported by \"perf buildid-cache -l\""
+ exit 1
+ fi
+
+ if [ -n "${perf_data}" ]; then
+ ${perf} buildid-list -i ${perf_data} | grep -q ${id}
+ if [ $? -ne 0 ]; then
+ echo "failed: ${id} is not reported by \"perf buildid-list -i ${perf_data}\""
+ exit 1
+ fi
+ fi
+
+ echo "OK for ${1}"
+}
+
+test_add()
+{
+ build_id_dir=$(mktemp -d /tmp/perf_buildid_test.debug.XXX)
+ perf="perf --buildid-dir ${build_id_dir}"
+
+ ${perf} buildid-cache -v -a ${1}
+ if [ $? -ne 0 ]; then
+ echo "failed: add ${1} to build id cache"
+ exit 1
+ fi
+
+ check ${1}
+
+ rm -rf ${build_id_dir}
+}
+
+test_remove()
+{
+ build_id_dir=$(mktemp -d /tmp/perf_buildid_test.debug.XXX)
+ perf="perf --buildid-dir ${build_id_dir}"
+
+ ${perf} buildid-cache -v -a ${1}
+ if [ $? -ne 0 ]; then
+ echo "failed: add ${1} to build id cache"
+ exit 1
+ fi
+
+ id=$(get_build_id ${1})
+ if ! ${perf} buildid-cache -l | grep -q ${id}; then
+ echo "failed: ${id} not in cache"
+ exit 1
+ fi
+
+ ${perf} buildid-cache -v -r ${1}
+ if [ $? -ne 0 ]; then
+ echo "failed: remove ${id} from build id cache"
+ exit 1
+ fi
+
+ if ${perf} buildid-cache -l | grep -q ${id}; then
+ echo "failed: ${id} still in cache after remove"
+ exit 1
+ fi
+
+ echo "remove: OK"
+ rm -rf ${build_id_dir}
+}
+
+test_purge()
+{
+ build_id_dir=$(mktemp -d /tmp/perf_buildid_test.debug.XXX)
+ perf="perf --buildid-dir ${build_id_dir}"
+
+ id1=$(get_build_id ${ex_sha1})
+ ${perf} buildid-cache -v -a ${ex_sha1}
+ if ! ${perf} buildid-cache -l | grep -q ${id1}; then
+ echo "failed: ${id1} not in cache"
+ exit 1
+ fi
+
+ id2=$(get_build_id ${ex_md5})
+ ${perf} buildid-cache -v -a ${ex_md5}
+ if ! ${perf} buildid-cache -l | grep -q ${id2}; then
+ echo "failed: ${id2} not in cache"
+ exit 1
+ fi
+
+ # Purge by path
+ ${perf} buildid-cache -v -p ${ex_sha1}
+ if [ $? -ne 0 ]; then
+ echo "failed: purge build id cache of ${ex_sha1}"
+ exit 1
+ fi
+
+ ${perf} buildid-cache -v -p ${ex_md5}
+ if [ $? -ne 0 ]; then
+ echo "failed: purge build id cache of ${ex_md5}"
+ exit 1
+ fi
+
+ # Verify both are gone
+ if ${perf} buildid-cache -l | grep -q ${id1}; then
+ echo "failed: ${id1} still in cache after purge"
+ exit 1
+ fi
+
+ if ${perf} buildid-cache -l | grep -q ${id2}; then
+ echo "failed: ${id2} still in cache after purge"
+ exit 1
+ fi
+
+ echo "purge: OK"
+ rm -rf ${build_id_dir}
+}
+
+test_record()
+{
+ build_id_dir=$(mktemp -d /tmp/perf_buildid_test.debug.XXX)
+ perf="perf --buildid-dir ${build_id_dir}"
+
+ echo "running: perf record $*"
+ ${perf} record --buildid-all -o ${data} "$@" 1>${log_out} 2>${log_err}
+ if [ $? -ne 0 ]; then
+ echo "failed: record $*"
+ echo "see log: ${log_err}"
+ exit 1
+ fi
+
+ args="$*"
+ check ${args##* } ${data}
+
+ rm -f ${log_out} ${log_err}
+ rm -rf ${build_id_dir}
+ rm -rf ${data}
+}
+
+# add binaries manual via perf buildid-cache -a
+test_add ${ex_sha1}
+test_add ${ex_md5}
+if [ ${add_pe} -eq 1 ]; then
+ test_add ${ex_pe}
+fi
+
+# add binaries via perf record post processing
+test_record ${ex_sha1}
+test_record ${ex_md5}
+if [ ${run_pe} -eq 1 ]; then
+ test_record wine ${ex_pe}
+fi
+
+# remove binaries manually via perf buildid-cache -r
+test_remove ${ex_sha1}
+test_remove ${ex_md5}
+if [ ${add_pe} -eq 1 ]; then
+ test_remove ${ex_pe}
+fi
+
+# purge binaries manually via perf buildid-cache -p
+test_purge
+
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/c2c.sh b/tools/perf/tests/shell/c2c.sh
new file mode 100755
index 000000000..2471d4459
--- /dev/null
+++ b/tools/perf/tests/shell/c2c.sh
@@ -0,0 +1,62 @@
+#!/bin/bash
+# perf c2c tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata=$(mktemp /tmp/__perf_c2c_test.perf.data.XXXXX)
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${perfdata}".old
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+check_c2c_support() {
+ # Check if perf c2c record works.
+ if ! perf c2c record -o "${perfdata}" -- true > /dev/null 2>&1 ; then
+ return 1
+ fi
+ return 0
+}
+
+test_c2c_record_report() {
+ echo "c2c record and report test"
+ if ! check_c2c_support ; then
+ echo "c2c record and report test [Skipped: perf c2c record failed (possibly missing hardware support)]"
+ err=2
+ return
+ fi
+
+ # Run a workload that does some memory operations.
+ if ! perf c2c record -o "${perfdata}" -- perf test -w datasym 1 > /dev/null 2>&1 ; then
+ echo "c2c record and report test [Skipped: perf c2c record failed during workload]"
+ return
+ fi
+
+ if ! perf c2c report -i "${perfdata}" --stdio > /dev/null 2>&1 ; then
+ echo "c2c record and report test [Failed: report failed]"
+ err=1
+ return
+ fi
+
+ if ! perf c2c report -i "${perfdata}" -N > /dev/null 2>&1 ; then
+ echo "c2c record and report test [Failed: report -N failed]"
+ err=1
+ return
+ fi
+
+ echo "c2c record and report test [Success]"
+}
+
+test_c2c_record_report
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/common/check_all_lines_matched.pl b/tools/perf/tests/shell/common/check_all_lines_matched.pl
new file mode 100755
index 000000000..fded48959
--- /dev/null
+++ b/tools/perf/tests/shell/common/check_all_lines_matched.pl
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+# SPDX-License-Identifier: GPL-2.0
+
+@regexps = @ARGV;
+
+$max_printed_lines = 20;
+$max_printed_lines = $ENV{TESTLOG_ERR_MSG_MAX_LINES} if (defined $ENV{TESTLOG_ERR_MSG_MAX_LINES});
+
+$quiet = 1;
+$quiet = 0 if (defined $ENV{TESTLOG_VERBOSITY} && $ENV{TESTLOG_VERBOSITY} ge 2);
+
+$passed = 1;
+$lines_printed = 0;
+
+while (<STDIN>)
+{
+ s/\n//;
+
+ $line_matched = 0;
+ for $r (@regexps)
+ {
+ if (/$r/)
+ {
+ $line_matched = 1;
+ last;
+ }
+ }
+
+ unless ($line_matched)
+ {
+ if ($lines_printed++ < $max_printed_lines)
+ {
+ print "Line did not match any pattern: \"$_\"\n" unless $quiet;
+ }
+ $passed = 0;
+ }
+}
+
+exit ($passed == 0);
diff --git a/tools/perf/tests/shell/common/check_all_patterns_found.pl b/tools/perf/tests/shell/common/check_all_patterns_found.pl
new file mode 100755
index 000000000..11bdf1d34
--- /dev/null
+++ b/tools/perf/tests/shell/common/check_all_patterns_found.pl
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+# SPDX-License-Identifier: GPL-2.0
+
+@regexps = @ARGV;
+
+$quiet = 1;
+$quiet = 0 if (defined $ENV{TESTLOG_VERBOSITY} && $ENV{TESTLOG_VERBOSITY} ge 2);
+
+%found = ();
+$passed = 1;
+
+while (<STDIN>)
+{
+ s/\n//;
+
+ for $r (@regexps)
+ {
+ if (/$r/)
+ {
+ $found{$r} = 1; # FIXME: maybe add counters -- how many times was the regexp matched
+ }
+ }
+}
+
+for $r (@regexps)
+{
+ unless (exists $found{$r})
+ {
+ print "Regexp not found: \"$r\"\n" unless $quiet;
+ $passed = 0;
+ }
+}
+
+exit ($passed == 0);
diff --git a/tools/perf/tests/shell/common/check_errors_whitelisted.pl b/tools/perf/tests/shell/common/check_errors_whitelisted.pl
new file mode 100755
index 000000000..c57d355dd
--- /dev/null
+++ b/tools/perf/tests/shell/common/check_errors_whitelisted.pl
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+# SPDX-License-Identifier: GPL-2.0
+
+$whitelist_file = shift;
+
+if (defined $whitelist_file)
+{
+ open (INFILE, $whitelist_file) or die "Checker error: Unable to open the whitelist file: $whitelist_file\n";
+ @regexps = <INFILE>;
+ close INFILE or die "Checker error: Unable to close the whitelist file: $whitelist_file\n";
+}
+else
+{
+ @regexps = ();
+}
+
+$max_printed_lines = 20;
+$max_printed_lines = $ENV{TESTLOG_ERR_MSG_MAX_LINES} if (defined $ENV{TESTLOG_ERR_MSG_MAX_LINES});
+
+$quiet = 1;
+$quiet = 0 if (defined $ENV{TESTLOG_VERBOSITY} && $ENV{TESTLOG_VERBOSITY} ge 2);
+
+$passed = 1;
+$lines_printed = 0;
+
+while (<STDIN>)
+{
+ s/\n//;
+
+ $line_matched = 0;
+ for $r (@regexps)
+ {
+ chomp $r;
+ if (/$r/)
+ {
+ $line_matched = 1;
+ last;
+ }
+ }
+
+ unless ($line_matched)
+ {
+ if ($lines_printed++ < $max_printed_lines)
+ {
+ print "Line did not match any pattern: \"$_\"\n" unless $quiet;
+ }
+ $passed = 0;
+ }
+}
+
+exit ($passed == 0);
diff --git a/tools/perf/tests/shell/common/check_no_patterns_found.pl b/tools/perf/tests/shell/common/check_no_patterns_found.pl
new file mode 100755
index 000000000..770999e87
--- /dev/null
+++ b/tools/perf/tests/shell/common/check_no_patterns_found.pl
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+# SPDX-License-Identifier: GPL-2.0
+
+@regexps = @ARGV;
+
+$quiet = 1;
+$quiet = 0 if (defined $ENV{TESTLOG_VERBOSITY} && $ENV{TESTLOG_VERBOSITY} ge 2);
+
+%found = ();
+$passed = 1;
+
+while (<STDIN>)
+{
+ s/\n//;
+
+ for $r (@regexps)
+ {
+ if (/$r/)
+ {
+ $found{$r} = 1;
+ }
+ }
+}
+
+for $r (@regexps)
+{
+ if (exists $found{$r})
+ {
+ print "Regexp found: \"$r\"\n" unless $quiet;
+ $passed = 0;
+ }
+}
+
+exit ($passed == 0);
diff --git a/tools/perf/tests/shell/common/init.sh b/tools/perf/tests/shell/common/init.sh
new file mode 100644
index 000000000..cbfc78bec
--- /dev/null
+++ b/tools/perf/tests/shell/common/init.sh
@@ -0,0 +1,143 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# init.sh
+# Author: Michael Petlan <mpetlan@redhat.com>
+#
+# Description:
+#
+# This file should be used for initialization of basic functions
+# for checking, reporting results etc.
+#
+#
+
+
+. "$(dirname $0)/../common/settings.sh"
+. "$(dirname $0)/../common/patterns.sh"
+
+THIS_TEST_NAME=`basename $0 .sh`
+
+_echo()
+{
+ test "$TESTLOG_VERBOSITY" -ne 0 && echo -e "$@"
+}
+
+print_results()
+{
+ PERF_RETVAL="$1"; shift
+ CHECK_RETVAL="$1"; shift
+ FAILURE_REASON=""
+ TASK_COMMENT="$*"
+ if [ $PERF_RETVAL -eq 0 ] && [ $CHECK_RETVAL -eq 0 ]; then
+ _echo "$MPASS-- [ PASS ] --$MEND $TEST_NAME :: $THIS_TEST_NAME :: $TASK_COMMENT"
+ return 0
+ else
+ if [ $PERF_RETVAL -ne 0 ]; then
+ FAILURE_REASON="command exitcode"
+ fi
+ if [ $CHECK_RETVAL -ne 0 ]; then
+ test -n "$FAILURE_REASON" && FAILURE_REASON="$FAILURE_REASON + "
+ FAILURE_REASON="$FAILURE_REASON""output regexp parsing"
+ fi
+ _echo "$MFAIL-- [ FAIL ] --$MEND $TEST_NAME :: $THIS_TEST_NAME :: $TASK_COMMENT ($FAILURE_REASON)"
+ return 1
+ fi
+}
+
+print_overall_results()
+{
+ RETVAL="$1"; shift
+ TASK_COMMENT="$*"
+ test -n "$TASK_COMMENT" && TASK_COMMENT=":: $TASK_COMMENT"
+
+ if [ $RETVAL -eq 0 ]; then
+ _echo "$MALLPASS## [ PASS ] ##$MEND $TEST_NAME :: $THIS_TEST_NAME SUMMARY"
+ else
+ _echo "$MALLFAIL## [ FAIL ] ##$MEND $TEST_NAME :: $THIS_TEST_NAME SUMMARY :: $RETVAL failures found $TASK_COMMENT"
+ fi
+ return $RETVAL
+}
+
+print_testcase_skipped()
+{
+ TASK_COMMENT="$*"
+ _echo "$MSKIP-- [ SKIP ] --$MEND $TEST_NAME :: $THIS_TEST_NAME :: $TASK_COMMENT :: testcase skipped"
+ return 0
+}
+
+print_overall_skipped()
+{
+ _echo "$MSKIP## [ SKIP ] ##$MEND $TEST_NAME :: $THIS_TEST_NAME :: testcase skipped"
+ return 0
+}
+
+print_warning()
+{
+ WARN_COMMENT="$*"
+ _echo "$MWARN-- [ WARN ] --$MEND $TEST_NAME :: $THIS_TEST_NAME :: $WARN_COMMENT"
+ return 0
+}
+
+# this function should skip a testcase if the testsuite is not run in
+# a runmode that fits the testcase --> if the suite runs in BASIC mode
+# all STANDARD and EXPERIMENTAL testcases will be skipped; if the suite
+# runs in STANDARD mode, all EXPERIMENTAL testcases will be skipped and
+# if the suite runs in EXPERIMENTAL mode, nothing is skipped
+consider_skipping()
+{
+ TESTCASE_RUNMODE="$1"
+ # the runmode of a testcase needs to be at least the current suite's runmode
+ if [ $PERFTOOL_TESTSUITE_RUNMODE -lt $TESTCASE_RUNMODE ]; then
+ print_overall_skipped
+ exit 2
+ fi
+}
+
+detect_baremetal()
+{
+ # return values:
+ # 0 = bare metal
+ # 1 = virtualization detected
+ # 2 = unknown state
+ VIRT=`systemd-detect-virt 2>/dev/null`
+ test $? -eq 127 && return 2
+ test "$VIRT" = "none"
+}
+
+detect_intel()
+{
+ # return values:
+ # 0 = is Intel
+ # 1 = is not Intel or unknown
+ grep "vendor_id" < /proc/cpuinfo | grep -q "GenuineIntel"
+}
+
+detect_amd()
+{
+ # return values:
+ # 0 = is AMD
+ # 1 = is not AMD or unknown
+ grep "vendor_id" < /proc/cpuinfo | grep -q "AMD"
+}
+
+# base probe utility
+check_kprobes_available()
+{
+ test -e /sys/kernel/debug/tracing/kprobe_events
+}
+
+check_uprobes_available()
+{
+ test -e /sys/kernel/debug/tracing/uprobe_events
+}
+
+clear_all_probes()
+{
+ echo 0 > /sys/kernel/debug/tracing/events/enable
+ check_kprobes_available && echo > /sys/kernel/debug/tracing/kprobe_events
+ check_uprobes_available && echo > /sys/kernel/debug/tracing/uprobe_events
+}
+
+check_sdt_support()
+{
+ $CMD_PERF list sdt | grep sdt > /dev/null 2> /dev/null
+}
diff --git a/tools/perf/tests/shell/common/patterns.sh b/tools/perf/tests/shell/common/patterns.sh
new file mode 100644
index 000000000..21dab25c7
--- /dev/null
+++ b/tools/perf/tests/shell/common/patterns.sh
@@ -0,0 +1,268 @@
+# SPDX-License-Identifier: GPL-2.0
+
+export RE_NUMBER="[0-9\.]+"
+# Number
+# Examples:
+# 123.456
+
+
+export RE_NUMBER_HEX="[0-9A-Fa-f]+"
+# Hexadecimal number
+# Examples:
+# 1234
+# a58d
+# aBcD
+# deadbeef
+
+
+export RE_DATE_YYYYMMDD="[0-9]{4}-(?:(?:01|03|05|07|08|10|12)-(?:[0-2][0-9]|3[0-1])|02-[0-2][0-9]|(?:(?:04|06|09|11)-(?:[0-2][0-9]|30)))"
+# Date in YYYY-MM-DD form
+# Examples:
+# 1990-02-29
+# 0015-07-31
+# 2456-12-31
+#! 2012-13-01
+#! 1963-09-31
+
+
+export RE_TIME="(?:[0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]"
+# Time
+# Examples:
+# 15:12:27
+# 23:59:59
+#! 24:00:00
+#! 11:25:60
+#! 17:60:15
+
+
+export RE_DATE_TIME="\w+\s+\w+\s+$RE_NUMBER\s+$RE_TIME\s+$RE_NUMBER"
+# Time and date
+# Examples:
+# Wed Feb 12 10:46:26 2020
+# Mon Mar 2 13:27:06 2020
+#! St úno 12 10:57:21 CET 2020
+#! Po úno 14 15:17:32 2010
+
+
+export RE_ADDRESS="0x$RE_NUMBER_HEX"
+# Memory address
+# Examples:
+# 0x123abc
+# 0xffffffff9abe8ae8
+# 0x0
+
+
+export RE_ADDRESS_NOT_NULL="0x[0-9A-Fa-f]*[1-9A-Fa-f]+[0-9A-Fa-f]*"
+# Memory address (not NULL)
+# Examples:
+# 0xffffffff9abe8ae8
+#! 0x0
+#! 0x0000000000000000
+
+export RE_PROCESS_PID="[^\/]+\/\d+"
+# A process with PID
+# Example:
+# sleep/4102
+# test_overhead./866185
+# in:imjournal/1096
+# random#$& test/866607
+
+export RE_EVENT_ANY="[\w\-\:\/_=,]+"
+# Name of any event (universal)
+# Examples:
+# cpu-cycles
+# cpu/event=12,umask=34/
+# r41e1
+# nfs:nfs_getattr_enter
+
+
+export RE_EVENT="[\w\-:_]+"
+# Name of an usual event
+# Examples:
+# cpu-cycles
+
+
+export RE_EVENT_RAW="r$RE_NUMBER_HEX"
+# Specification of a raw event
+# Examples:
+# r41e1
+# r1a
+
+
+export RE_EVENT_CPU="cpu/(\w+=$RE_NUMBER_HEX,?)+/p*"
+# Specification of a CPU event
+# Examples:
+# cpu/event=12,umask=34/pp
+
+
+export RE_EVENT_UNCORE="uncore/[\w_]+/"
+# Specification of an uncore event
+# Examples:
+# uncore/qhl_request_local_reads/
+
+
+export RE_EVENT_SUBSYSTEM="[\w\-]+:[\w\-]+"
+# Name of an event from subsystem
+# Examples:
+# ext4:ext4_ordered_write_end
+# sched:sched_switch
+
+
+export RE_FILE_NAME="[\w\+\.-]+"
+# A filename
+# Examples:
+# libstdc++.so.6
+#! some/path
+
+
+export RE_PATH_ABSOLUTE="(?:\/$RE_FILE_NAME)+"
+# A full filepath
+# Examples:
+# /usr/lib64/somelib.so.5.4.0
+# /lib/modules/4.3.0-rc5/kernel/fs/xfs/xfs.ko
+# /usr/bin/mv
+#! some/relative/path
+#! ./some/relative/path
+
+
+export RE_PATH="(?:$RE_FILE_NAME)?$RE_PATH_ABSOLUTE"
+# A filepath
+# Examples:
+# /usr/lib64/somelib.so.5.4.0
+# /lib/modules/4.3.0-rc5/kernel/fs/xfs/xfs.ko
+# ./.emacs
+# src/fs/file.c
+
+
+export RE_DSO="(?:$RE_PATH_ABSOLUTE(?: \(deleted\))?|\[kernel\.kallsyms\]|\[unknown\]|\[vdso\]|\[kernel\.vmlinux\][\.\w]*)"
+# A DSO name in various result tables
+# Examples:
+# /usr/lib64/somelib.so.5.4.0
+# /usr/bin/somebinart (deleted)
+# /lib/modules/4.3.0-rc5/kernel/fs/xfs/xfs.ko
+# [kernel.kallsyms]
+# [kernel.vmlinux]
+# [vdso]
+# [unknown]
+
+
+export RE_LINE_COMMENT="^#.*"
+# A comment line
+# Examples:
+# # Started on Thu Sep 10 11:43:00 2015
+
+
+export RE_LINE_EMPTY="^\s*$"
+# An empty line with possible whitespaces
+# Examples:
+#
+
+
+export RE_LINE_RECORD1="^\[\s+perf\s+record:\s+Woken up $RE_NUMBER times? to write data\s+\].*$"
+# The first line of perf-record "OK" output
+# Examples:
+# [ perf record: Woken up 1 times to write data ]
+
+
+export RE_LINE_RECORD2="^\[\s+perf\s+record:\s+Captured and wrote $RE_NUMBER\s*MB\s+(?:[\w\+\.-]*(?:$RE_PATH)?\/)?perf\.data(?:\.\d+)?\s*\(~?$RE_NUMBER samples\)\s+\].*$"
+# The second line of perf-record "OK" output
+# Examples:
+# [ perf record: Captured and wrote 0.405 MB perf.data (109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB perf.data (~109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB /some/temp/dir/perf.data (109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB ./perf.data (109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB ./perf.data.3 (109 samples) ]
+
+
+export RE_LINE_RECORD2_TOLERANT="^\[\s+perf\s+record:\s+Captured and wrote $RE_NUMBER\s*MB\s+(?:[\w\+\.-]*(?:$RE_PATH)?\/)?perf\.data(?:\.\d+)?\s*(?:\(~?$RE_NUMBER samples\))?\s+\].*$"
+# The second line of perf-record "OK" output, even no samples is OK here
+# Examples:
+# [ perf record: Captured and wrote 0.405 MB perf.data (109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB perf.data (~109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB /some/temp/dir/perf.data (109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB ./perf.data (109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB ./perf.data.3 (109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB perf.data ]
+
+
+export RE_LINE_RECORD2_TOLERANT_FILENAME="^\[\s+perf\s+record:\s+Captured and wrote $RE_NUMBER\s*MB\s+(?:[\w\+\.-]*(?:$RE_PATH)?\/)?perf\w*\.data(?:\.\d+)?\s*\(~?$RE_NUMBER samples\)\s+\].*$"
+# The second line of perf-record "OK" output
+# Examples:
+# [ perf record: Captured and wrote 0.405 MB perf.data (109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB perf_ls.data (~109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB perf_aNyCaSe.data (109 samples) ]
+# [ perf record: Captured and wrote 0.405 MB ./perfdata.data.3 (109 samples) ]
+#! [ perf record: Captured and wrote 0.405 MB /some/temp/dir/my_own.data (109 samples) ]
+#! [ perf record: Captured and wrote 0.405 MB ./UPPERCASE.data (109 samples) ]
+#! [ perf record: Captured and wrote 0.405 MB ./aNyKiNDoF.data.3 (109 samples) ]
+#! [ perf record: Captured and wrote 0.405 MB perf.data ]
+
+
+export RE_LINE_TRACE_FULL="^\s*$RE_NUMBER\s*\(\s*$RE_NUMBER\s*ms\s*\):\s*$RE_PROCESS_PID\s+.*\)\s+=\s+(:?\-?$RE_NUMBER|0x$RE_NUMBER_HEX).*$"
+# A line of perf-trace output
+# Examples:
+# 0.115 ( 0.005 ms): sleep/4102 open(filename: 0xd09e2ab2, flags: CLOEXEC ) = 3
+# 0.157 ( 0.005 ms): sleep/4102 mmap(len: 3932736, prot: EXEC|READ, flags: PRIVATE|DENYWRITE, fd: 3 ) = 0x7f89d0605000
+#! 0.115 ( 0.005 ms): sleep/4102 open(filename: 0xd09e2ab2, flags: CLOEXEC ) =
+
+export RE_LINE_TRACE_ONE_PROC="^\s*$RE_NUMBER\s*\(\s*$RE_NUMBER\s*ms\s*\):\s*\w+\(.*\)\s+=\s+(?:\-?$RE_NUMBER|0x$RE_NUMBER_HEX).*$"
+# A line of perf-trace output
+# Examples:
+# 0.115 ( 0.005 ms): open(filename: 0xd09e2ab2, flags: CLOEXEC ) = 3
+# 0.157 ( 0.005 ms): mmap(len: 3932736, prot: EXEC|READ, flags: PRIVATE|DENYWRITE, fd: 3 ) = 0x7f89d0605000
+#! 0.115 ( 0.005 ms): open(filename: 0xd09e2ab2, flags: CLOEXEC ) =
+
+export RE_LINE_TRACE_CONTINUED="^\s*(:?$RE_NUMBER|\?)\s*\(\s*($RE_NUMBER\s*ms\s*)?\):\s*($RE_PROCESS_PID\s*)?\.\.\.\s*\[continued\]:\s+\w+\(\).*\s+=\s+(?:\-?$RE_NUMBER|0x$RE_NUMBER_HEX).*$"
+# A line of perf-trace output
+# Examples:
+# 0.000 ( 0.000 ms): ... [continued]: nanosleep()) = 0
+# 0.000 ( 0.000 ms): ... [continued]: nanosleep()) = 0x00000000
+# ? ( ): packagekitd/94838 ... [continued]: poll()) = 0 (Timeout)
+#! 0.000 ( 0.000 ms): ... [continued]: nanosleep()) =
+
+export RE_LINE_TRACE_UNFINISHED="^\s*$RE_NUMBER\s*\(\s*\):\s*$RE_PROCESS_PID\s+.*\)\s+\.\.\.\s*$"
+# A line of perf-trace output
+# Examples:
+# 901.040 ( ): in:imjournal/1096 ppoll(ufds: 0x7f701a5adb70, nfds: 1, tsp: 0x7f701a5adaf0, sigsetsize: 8) ...
+# 613.727 ( ): gmain/1099 poll(ufds: 0x56248f6b64b0, nfds: 2, timeout_msecs: 3996) ...
+
+export RE_LINE_TRACE_SUMMARY_HEADER="\s*syscall\s+calls\s+(?:errors\s+)?total\s+min\s+avg\s+max\s+stddev"
+# A header of a perf-trace summary table
+# Example:
+# syscall calls total min avg max stddev
+# syscall calls errors total min avg max stddev
+
+
+export RE_LINE_TRACE_SUMMARY_CONTENT="^\s*\w+\s+(?:$RE_NUMBER\s+){5,6}$RE_NUMBER%"
+# A line of a perf-trace summary table
+# Example:
+# open 3 0.017 0.005 0.006 0.007 10.90%
+# openat 2 0 0.017 0.008 0.009 0.010 12.29%
+
+
+export RE_LINE_REPORT_CONTENT="^\s+$RE_NUMBER%\s+\w+\s+\S+\s+\S+\s+\S+" # FIXME
+# A line from typicap perf report --stdio output
+# Example:
+# 100.00% sleep [kernel.vmlinux] [k] syscall_return_slowpath
+
+
+export RE_TASK="\s+[\w~\/ \.\+:#-]+(?:\[-1(?:\/\d+)?\]|\[\d+(?:\/\d+)?\])"
+# A name of a task used for perf sched timehist -s
+# Example:
+# sleep[62755]
+# runtest.sh[62762]
+# gmain[705/682]
+# xfsaild/dm-0[495]
+# kworker/u8:1-ev[62714]
+# :-1[-1/62756]
+# :-1[-1]
+# :-1[62756]
+
+
+export RE_SEGFAULT=".*(?:Segmentation\sfault|SIGSEGV|\score\s|dumped|segfault).*"
+# Possible variations of the segfault message
+# Example:
+# /bin/bash: line 1: 32 Segmentation fault timeout 15s
+# Segmentation fault (core dumped)
+# Program terminated with signal SIGSEGV
+#! WARNING: 12323431 isn't a 'cpu_core', please use a CPU list in the 'cpu_core' range (0-15)
diff --git a/tools/perf/tests/shell/common/settings.sh b/tools/perf/tests/shell/common/settings.sh
new file mode 100644
index 000000000..cba1b338f
--- /dev/null
+++ b/tools/perf/tests/shell/common/settings.sh
@@ -0,0 +1,105 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# settings.sh
+# Author: Michael Petlan <mpetlan@redhat.com>
+#
+# Description:
+#
+# This file contains global settings for the whole testsuite.
+# Its purpose is to make it easier when it is necessary i.e. to
+# change the usual sample command which is used in all of the tests
+# in many files.
+#
+# This file is intended to be sourced in the tests.
+#
+
+#### which perf to use in the testing
+export CMD_PERF=${CMD_PERF:-`which perf`}
+
+#### basic programs examinated by perf
+export CMD_BASIC_SLEEP="sleep 0.1"
+export CMD_QUICK_SLEEP="sleep 0.01"
+export CMD_LONGER_SLEEP="sleep 2"
+export CMD_DOUBLE_LONGER_SLEEP="sleep 4"
+export CMD_VERY_LONG_SLEEP="sleep 30"
+export CMD_SIMPLE="true"
+
+#### testsuite run mode
+# define constants:
+export RUNMODE_BASIC=0
+export RUNMODE_STANDARD=1
+export RUNMODE_EXPERIMENTAL=2
+# default runmode is STANDARD
+export PERFTOOL_TESTSUITE_RUNMODE=${PERFTOOL_TESTSUITE_RUNMODE:-$RUNMODE_STANDARD}
+
+#### common settings
+export TESTLOG_VERBOSITY=${TESTLOG_VERBOSITY:-2}
+export TESTLOG_FORCE_COLOR=${TESTLOG_FORCE_COLOR:-n}
+export TESTLOG_ERR_MSG_MAX_LINES=${TESTLOG_ERR_MSG_MAX_LINES:-20}
+export TESTLOG_CLEAN=${TESTLOG_CLEAN:-y}
+
+#### other environment-related settings
+export TEST_IGNORE_MISSING_PMU=${TEST_IGNORE_MISSING_PMU:-n}
+
+#### clear locale
+export LC_ALL=C
+
+#### colors
+if [ -t 1 ] || [ "$TESTLOG_FORCE_COLOR" = "yes" ]; then
+ export MPASS="\e[32m"
+ export MALLPASS="\e[1;32m"
+ export MFAIL="\e[31m"
+ export MALLFAIL="\e[1;31m"
+ export MWARN="\e[1;35m"
+ export MSKIP="\e[33m"
+ export MHIGH="\e[1;33m"
+ export MEND="\e[m"
+else
+ export MPASS=""
+ export MALLPASS=""
+ export MFAIL=""
+ export MALLFAIL=""
+ export MWARN=""
+ export MSKIP=""
+ export MHIGH=""
+ export MEND=""
+fi
+
+### general info
+DIR_PATH=`dirname "$(readlink -e "$0")"`
+
+TEST_NAME=`basename $DIR_PATH | sed 's/base/perf/'`; export TEST_NAME
+MY_ARCH=`arch`; export MY_ARCH
+
+# storing logs and temporary files variables
+if [ -n "$PERFSUITE_RUN_DIR" ]; then
+ # when $PERFSUITE_RUN_DIR is set to something, all the logs and temp files will be placed there
+ # --> the $PERFSUITE_RUN_DIR/perf_something/examples and $PERFSUITE_RUN_DIR/perf_something/logs
+ # dirs will be used for that
+ PERFSUITE_RUN_DIR=`readlink -f $PERFSUITE_RUN_DIR`; export PERFSUITE_RUN_DIR
+ export CURRENT_TEST_DIR="$PERFSUITE_RUN_DIR/$TEST_NAME"
+ export MAKE_TARGET_DIR="$CURRENT_TEST_DIR/examples"
+ export LOGS_DIR="$CURRENT_TEST_DIR/logs"
+ export HEADER_TAR_DIR="$CURRENT_TEST_DIR/header_tar"
+ test -d "$CURRENT_TEST_DIR" || mkdir -p "$CURRENT_TEST_DIR"
+ test -d "$LOGS_DIR" || mkdir -p "$LOGS_DIR"
+else
+ # when $PERFSUITE_RUN_DIR is not set, logs will be placed here
+ export CURRENT_TEST_DIR="."
+ export LOGS_DIR="."
+ export HEADER_TAR_DIR="./header_tar"
+fi
+
+
+#### test parametrization
+if [ ! -d ./common ]; then
+ # set parameters based on runmode
+ if [ -f ../common/parametrization.$PERFTOOL_TESTSUITE_RUNMODE.sh ]; then
+ # shellcheck source=/dev/null
+ . ../common/parametrization.$PERFTOOL_TESTSUITE_RUNMODE.sh
+ fi
+ # if some parameters haven't been set until now, set them to default
+ if [ -f ../common/parametrization.sh ]; then
+ . ../common/parametrization.sh
+ fi
+fi
diff --git a/tools/perf/tests/shell/coresight/callchain.sh b/tools/perf/tests/shell/coresight/callchain.sh
new file mode 100755
index 000000000..13cca7dc1
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/callchain.sh
@@ -0,0 +1,172 @@
+#!/bin/bash
+# CoreSight synthesized callchain (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+glb_err=1
+
+if ! tmpdir=$(mktemp -d /tmp/perf-cs-callchain-test.XXXXXX); then
+ echo "mktemp failed"
+ exit 1
+fi
+
+cleanup_files()
+{
+ rm -rf "$tmpdir"
+}
+
+trap cleanup_files EXIT
+trap 'cleanup_files; exit $glb_err' TERM INT
+
+skip_if_system_is_not_ready()
+{
+ perf list | grep -Pzq 'cs_etm//' || {
+ echo "[Skip] cs_etm event is not available" >&2
+ return 2
+ }
+
+ # Requires root for trace in kernel
+ [ "$(id -u)" = 0 ] || {
+ echo "[Skip] No root permission" >&2
+ return 2
+ }
+
+ return 0
+}
+
+record_trace()
+{
+ local data=$1
+ local script=$2
+
+ local cf="$tmpdir/ctl"
+ local af="$tmpdir/ack"
+
+ mkfifo "$cf" "$af"
+
+ perf record -o "$data" -e cs_etm// --per-thread -D -1 --control fifo:"$cf","$af" -- \
+ perf test --record-ctl fifo:"$cf","$af" -w callchain >/dev/null 2>&1 &&
+
+ # It is safe to use 'i3i' with a three-instruction interval, since the
+ # workload is compiled with -O0.
+ perf script --itrace=g16i3il64 -i "$data" > "$script"
+}
+
+callchain_regex_1()
+{
+ printf '%s' \
+'perf[[:space:]]+[0-9]+[[:space:]]+\[[0-9]+\][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain_foo\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'([[:space:]]+[[:xdigit:]]+ .*\n)*'
+}
+
+callchain_regex_2()
+{
+ printf '%s' \
+'perf[[:space:]]+[0-9]+[[:space:]]+\[[0-9]+\][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain_foo\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'([[:space:]]+[[:xdigit:]]+ .*\n)*'
+}
+
+callchain_regex_3()
+{
+ printf '%s' \
+'perf[[:space:]]+[0-9]+[[:space:]]+\[[0-9]+\][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\
+'[[:space:]]+[[:xdigit:]]+ syscall(@plt)?\+0x[[:xdigit:]]+ \(.*\)\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain_foo\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'([[:space:]]+[[:xdigit:]]+ .*\n)*'
+}
+
+callchain_regex_4()
+{
+ printf '%s' \
+'perf[[:space:]]+[0-9]+[[:space:]]+\[[0-9]+\][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\
+'[[:space:]]+[[:xdigit:]]+ .*\+0x[[:xdigit:]]+ \(\[kernel\.kallsyms\]\)\n'\
+'[[:space:]]+[[:xdigit:]]+ syscall(@plt)?\+0x[[:xdigit:]]+ \(.*\)\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain_foo\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'[[:space:]]+[[:xdigit:]]+ callchain\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
+'([[:space:]]+[[:xdigit:]]+ .*\n)*'
+}
+
+find_after_line()
+{
+ local regex="$1"
+ local file="$2"
+ local start="$3"
+ local offset
+ local line
+
+ # Search in byte offset
+ offset=$(
+ tail -n +"$start" "$file" |
+ grep -Pzob -m1 "$regex" |
+ tr '\0' '\n' |
+ sed -n 's/^\([0-9][0-9]*\):.*/\1/p;q'
+ )
+
+ if [ -z "$offset" ]; then
+ echo "Failed to match regex after line $start" >&2
+ echo "Regex:" >&2
+ printf '%s\n' "$regex" >&2
+ echo "Context from line $start:" >&2
+ sed -n "${start},$((start + 100))p" "$file" >&2
+ return 1
+ fi
+
+ # Convert from offset to line
+ line=$(
+ tail -n +"$start" "$file" |
+ head -c "$offset" |
+ wc -l
+ )
+
+ echo "$((start + line))"
+}
+
+check_callchain_flow()
+{
+ local file="$1"
+ local l1 l2 l3 l4 l5 l6 l7
+
+ # Callchain push
+ l1=$(find_after_line "$(callchain_regex_1)" "$file" 1) || return 1
+ l2=$(find_after_line "$(callchain_regex_2)" "$file" "$((l1 + 1))") || return 1
+ l3=$(find_after_line "$(callchain_regex_3)" "$file" "$((l2 + 1))") || return 1
+ l4=$(find_after_line "$(callchain_regex_4)" "$file" "$((l3 + 1))") || return 1
+
+ # Callchain pop
+ l5=$(find_after_line "$(callchain_regex_3)" "$file" "$((l4 + 1))") || return 1
+ l6=$(find_after_line "$(callchain_regex_2)" "$file" "$((l5 + 1))") || return 1
+ l7=$(find_after_line "$(callchain_regex_1)" "$file" "$((l6 + 1))") || return 1
+
+ echo "Callchain flow matched:"
+ echo " l1=$l1 l2=$l2 l3=$l3 l4=$l4 l5=$l5 l6=$l6 l7=$l7"
+
+ return 0
+}
+
+run_test()
+{
+ local data=$tmpdir/perf.data
+ local script=$tmpdir/perf.script
+
+ if ! record_trace "$data" "$script"; then
+ echo "perf record/script failed"
+ return
+ fi
+
+ check_callchain_flow "$script" || return
+
+ glb_err=0
+}
+
+skip_if_system_is_not_ready || exit 2
+
+run_test
+
+exit $glb_err
diff --git a/tools/perf/tests/shell/coresight/concurrent_threads.sh b/tools/perf/tests/shell/coresight/concurrent_threads.sh
new file mode 100755
index 000000000..3349fff8c
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/concurrent_threads.sh
@@ -0,0 +1,45 @@
+#!/bin/bash -e
+# CoreSight concurrent threads (exclusive)
+
+# SPDX-License-Identifier: GPL-2.0
+
+# If CoreSight is not available, skip the test
+perf list pmu | grep -q cs_etm || exit 2
+
+tmpdir=$(mktemp -d /tmp/__perf_test.coresight_concurrent_threads.XXXXX)
+
+cleanup() {
+ rm -rf "${tmpdir}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+cf="$tmpdir/ctl"
+af="$tmpdir/ack"
+mkfifo "$cf" "$af"
+
+nthreads=10
+
+# Timestamps off to reduce trace size, start disabled and use the control FIFO
+# to only record the workload and not startup.
+perf record -o "$tmpdir/data" -e cs_etm/timestamp=0/u -D -1 --control fifo:"$cf","$af" \
+ -- perf test --record-ctl fifo:"$cf","$af" -w named_threads $nthreads 1 > /dev/null 2>&1
+
+perf script -i "$tmpdir/data" > "$tmpdir/script" 2>/dev/null
+
+# Check all threads were traced and they have the correct thread name and symbol
+for i in $(seq 1 $nthreads); do
+ if ! grep -q "thread${i} .* named_threads_thread${i}" "$tmpdir/script"; then
+ echo "Error: thread${i} missing" >&2
+ cleanup
+ exit 1
+ fi
+done
+
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/coresight/context_switch_thread.sh b/tools/perf/tests/shell/coresight/context_switch_thread.sh
new file mode 100755
index 000000000..2b9c44b86
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/context_switch_thread.sh
@@ -0,0 +1,69 @@
+#!/bin/bash -e
+# CoreSight context switch thread attribution (exclusive)
+
+# SPDX-License-Identifier: GPL-2.0
+
+# If CoreSight is not available, skip the test
+perf list pmu | grep -q cs_etm || exit 2
+
+if [ "$(id -u)" != 0 ]; then
+ # Requires root for "-C 0" in record command
+ echo "[Skip] No root permission"
+ exit 2
+fi
+
+tmpdir=$(mktemp -d /tmp/__perf_test.coresight_context_switch.XXXXX)
+
+cleanup() {
+ rm -rf "${tmpdir}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+check_samples() {
+ owner_samples=$(grep -c "proc1.*context_switch_loop_proc1" "$tmpdir/script" || true)
+ next_samples=$(grep -c "proc2.*context_switch_loop_proc2" "$tmpdir/script" || true)
+
+ if [ "$owner_samples" -eq 0 ] || [ "$next_samples" -eq 0 ]; then
+ echo "No samples found"
+ cleanup
+ exit 1
+ fi
+
+ if grep "proc2.*context_switch_loop_proc1" "$tmpdir/script"; then
+ echo "Thread1 symbol was attributed to proc2"
+ cleanup
+ exit 1
+ fi
+
+ if grep "proc1.*context_switch_loop_proc2" "$tmpdir/script"; then
+ echo "Thread2 symbol was attributed to proc1"
+ cleanup
+ exit 1
+ fi
+}
+
+cf="$tmpdir/ctl"
+af="$tmpdir/ack"
+mkfifo "$cf" "$af"
+
+# Pin to one CPU so the two threads alternate running but record into the same
+# trace buffer. Start disabled and use the control FIFO to only record the
+# workload and not startup.
+perf record -o "$tmpdir/data" -e cs_etm/timestamp=0/u -C 0 -D -1 --control fifo:"$cf","$af" -- \
+ taskset --cpu-list 0 perf test --record-ctl fifo:"$cf","$af" \
+ -w context_switch_loop > /dev/null 2>&1
+
+# Test both instruction and branch sample generation modes.
+perf script -i "$tmpdir/data" --itrace=i4 -F comm,pid,tid,ip,sym > "$tmpdir/script" 2>/dev/null
+check_samples
+perf script -i "$tmpdir/data" --itrace=b -F comm,pid,tid,ip,sym > "$tmpdir/script" 2>/dev/null
+check_samples
+
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/coresight/deterministic.sh b/tools/perf/tests/shell/coresight/deterministic.sh
new file mode 100755
index 000000000..75d497305
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/deterministic.sh
@@ -0,0 +1,72 @@
+#!/bin/bash -e
+# CoreSight deterministic workload decode (exclusive)
+
+# SPDX-License-Identifier: GPL-2.0
+
+# If CoreSight is not available, skip the test
+perf list pmu | grep -q cs_etm || exit 2
+
+tmpdir=$(mktemp -d /tmp/__perf_test.coresight_deterministic.XXXXX)
+
+cleanup() {
+ rm -rf "${tmpdir}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+cf="$tmpdir/ctl"
+af="$tmpdir/ack"
+mkfifo "$cf" "$af"
+
+# Start disabled and use the control FIFO to only record the workload and not
+# startup.
+perf record -o "$tmpdir/data" -e cs_etm//u -D -1 --control fifo:"$cf","$af" -- \
+ perf test --record-ctl fifo:"$cf","$af" -w deterministic > /dev/null 2>&1
+
+perf script -i "$tmpdir/data" --itrace=i1i -F ip,srcline | \
+ grep "deterministic.c" | uniq > "$tmpdir/script" 2>/dev/null
+
+
+# Remove brace lines and call sites as they may not be hit or may have
+# extra hits after returning, depending on the compiler.
+sed -i \
+ -e '/deterministic.c:8$/d' \
+ -e '/deterministic.c:12$/d' \
+ -e '/deterministic.c:15$/d' \
+ -e '/deterministic.c:19$/d' \
+ -e '/deterministic.c:23$/d' \
+ -e '/deterministic.c:28$/d' \
+ -e '/deterministic.c:34$/d' \
+ -e '/deterministic.c:36$/d' \
+ -e '/deterministic.c:37$/d' \
+ "$tmpdir/script"
+
+cat > "$tmpdir/expected" << EOF
+ deterministic.c:24
+ deterministic.c:25
+ deterministic.c:26
+ deterministic.c:9
+ deterministic.c:10
+ deterministic.c:11
+ deterministic.c:30
+ deterministic.c:31
+ deterministic.c:32
+ deterministic.c:16
+ deterministic.c:17
+ deterministic.c:18
+EOF
+
+if ! diff -q "$tmpdir/script" "$tmpdir/expected"; then
+ echo "FAIL: line numbers don't match expected: "
+ head -n 100 "$tmpdir/script"
+ cleanup
+ exit 1
+fi
+
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/coresight/raw_dump_stress.sh b/tools/perf/tests/shell/coresight/raw_dump_stress.sh
new file mode 100755
index 000000000..bea70d825
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/raw_dump_stress.sh
@@ -0,0 +1,65 @@
+#!/bin/bash -e
+# CoreSight raw dump stress (exclusive)
+
+# SPDX-License-Identifier: GPL-2.0
+
+if [ "$(id -u)" != 0 ]; then
+ # Requires root for larger buffer size
+ echo "[Skip] No root permission"
+ exit 2
+fi
+
+# If CoreSight is not available, skip the test
+perf list pmu | grep -q cs_etm || exit 2
+
+tmpdir=$(mktemp -d /tmp/__perf_test.coresight_raw_dump_stress.XXXXX)
+
+cleanup() {
+ rm -r "${tmpdir}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+# Use exit snapshot to record 2M of trace to make about 80MB of raw dump data.
+echo "Recording..."
+perf record -e cs_etm/timestamp=0/u -m,2M -Se -o "$tmpdir/data" -- \
+ perf test -w brstack 20000 > /dev/null 2>&1
+
+# Test raw dump runs to completion but don't decode because that's too slow for
+# a test
+echo "Dumping raw trace..."
+perf report --dump-raw-trace -i "$tmpdir/data" 2>/dev/null > "$tmpdir/rawdump"
+
+# Get the size and offset of the first AUXTRACE buffer and the index of the last
+# packet in the raw dump.
+read -r size offset last_idx <<< "$(awk '
+ found && /PERF_RECORD_/ { exit }
+ /PERF_RECORD_AUXTRACE / { found = 1; size = $7; offset = $9; next }
+ found && /Idx:/ { last_idx = $1; gsub(/Idx:|;/, "", last_idx) }
+ END { if (last_idx) print size, offset, last_idx }
+' "$tmpdir/rawdump")"
+
+# The last Idx minus start offset should equal the size of the buffer if
+# everything was dumped. Allow 48 bytes difference to cover 3 frames: current
+# frame length, a partial frame and a final empty one, all of which aren't
+# dumped.
+#
+# TODO: for a single snapshot, offset should always be zero. However, we
+# currently output AUX records in snapshot mode when we shouldn't, which
+# increments the offset. Allow for that until it's fixed so we can test raw
+# dumping.
+decode_size=$((1 + last_idx - offset))
+if [ "$decode_size" -gt "$((size - 48))" ] && [ "$decode_size" -le "$((size))" ]; then
+ echo "PASS: AUXTRACE buffer length matches dumped packet index"
+ cleanup
+ exit 0
+fi
+
+echo "FAIL: AUXTRACE buffer length mismatch: size=$size offset=$offset last_idx=$last_idx decode_size=$decode_size"
+cleanup
+exit 1
diff --git a/tools/perf/tests/shell/coresight/test_arm_coresight.sh b/tools/perf/tests/shell/coresight/test_arm_coresight.sh
new file mode 100755
index 000000000..83295a8fe
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/test_arm_coresight.sh
@@ -0,0 +1,257 @@
+#!/bin/bash
+# Check Arm CoreSight trace data recording and synthesized samples (exclusive)
+
+# Uses the 'perf record' to record trace data with Arm CoreSight sinks;
+# then verify if there have any branch samples and instruction samples
+# are generated by CoreSight with 'perf script' and 'perf report'
+# commands.
+
+# SPDX-License-Identifier: GPL-2.0
+# Leo Yan <leo.yan@linaro.org>, 2020
+
+glb_err=0
+
+skip_if_no_cs_etm_event() {
+ perf list pmu | grep -q 'cs_etm//' && return 0
+
+ # cs_etm event doesn't exist
+ return 2
+}
+
+skip_if_no_cs_etm_event || exit 2
+
+if [ "$(id -u)" != 0 ]; then
+ # Requires root for -C and system wide tests
+ echo "[Skip] No root permission"
+ exit 2
+fi
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+file=$(mktemp /tmp/temporary_file.XXXXX)
+
+cleanup_files()
+{
+ rm -f ${perfdata}
+ rm -f ${file}
+ rm -f "${perfdata}.old"
+ trap - EXIT TERM INT
+ exit $glb_err
+}
+
+trap cleanup_files EXIT TERM INT
+
+record_touch_file() {
+ echo "Recording trace (only user mode) with path: CPU$2 => $1"
+ rm -f $file
+ perf record -o ${perfdata} -e cs_etm/@$1/u --per-thread \
+ -- taskset -c $2 touch $file > /dev/null 2>&1
+}
+
+perf_script_branch_samples() {
+ echo "Looking at perf.data file for dumping branch samples:"
+
+ # Below is an example of the branch samples dumping:
+ # touch 6512 1 branches:u: ffffb220824c strcmp+0xc (/lib/aarch64-linux-gnu/ld-2.27.so)
+ # touch 6512 1 branches:u: ffffb22082e0 strcmp+0xa0 (/lib/aarch64-linux-gnu/ld-2.27.so)
+ # touch 6512 1 branches:u: ffffb2208320 strcmp+0xe0 (/lib/aarch64-linux-gnu/ld-2.27.so)
+ perf script -F,-time -i ${perfdata} 2>&1 | \
+ grep -E " +$1 +[0-9]+ .* +branches:(.*:)? +" > /dev/null 2>&1
+}
+
+perf_report_instruction_samples() {
+ echo "Looking at perf.data file for instruction samples:"
+
+ # Below is an example of the instruction samples reporting:
+ # 68.12% touch libc-2.27.so [.] _dl_addr
+ # 5.80% touch libc-2.27.so [.] getenv
+ # 4.35% touch ld-2.27.so [.] _dl_fixup
+ perf report --itrace=i20i --stdio -i ${perfdata} 2>&1 | \
+ grep -E " +[0-9]+\.[0-9]+% +$1" > /dev/null 2>&1
+}
+
+arm_cs_report() {
+ if [ $2 != 0 ]; then
+ echo "$1: FAIL"
+ glb_err=$2
+ else
+ echo "$1: PASS"
+ fi
+}
+
+is_device_sink() {
+ # If the node of "enable_sink" is existed under the device path, this
+ # means the device is a sink device. Need to exclude 'tpiu' since it
+ # cannot support perf PMU.
+ echo "$1" | grep -E -q -v "tpiu"
+
+ if [ $? -eq 0 ] && [ -e "$1/enable_sink" ]; then
+
+ pmu_dev="/sys/bus/event_source/devices/cs_etm/sinks/$2"
+
+ # Warn if the device is not supported by PMU
+ if ! [ -f $pmu_dev ]; then
+ echo "PMU doesn't support $pmu_dev"
+ fi
+
+ return 0
+ fi
+
+ # Otherwise, it's not a sink device
+ return 1
+}
+
+arm_cs_iterate_devices() {
+ for dev in $1/connections/out\:*; do
+
+ # Skip testing if it's not a directory
+ ! [ -d $dev ] && continue;
+
+ # Read out its symbol link file name
+ path=`readlink -f $dev`
+
+ # Extract device name from path, e.g.
+ # path = '/sys/devices/platform/20010000.etf/tmc_etf0'
+ # `> device_name = 'tmc_etf0'
+ device_name=$(basename $path)
+
+ if is_device_sink $path $device_name; then
+
+ record_touch_file $device_name $2 &&
+ perf_script_branch_samples touch &&
+ perf_report_instruction_samples touch
+
+ err=$?
+ arm_cs_report "CoreSight path testing (CPU$2 -> $device_name)" $err
+ fi
+
+ arm_cs_iterate_devices $dev $2
+ done
+}
+
+arm_cs_etm_traverse_path_test() {
+ # Iterate for every ETM device
+ for dev in /sys/bus/event_source/devices/cs_etm/cpu*; do
+ # Canonicalize the path
+ dev=`readlink -f $dev`
+
+ # Find the ETM device belonging to which CPU
+ cpu=`cat $dev/cpu`
+
+ # Use depth-first search (DFS) to iterate outputs
+ arm_cs_iterate_devices $dev $cpu
+ done
+}
+
+arm_cs_etm_system_wide_test() {
+ echo "Recording trace with system wide mode"
+ perf record -o ${perfdata} -e cs_etm// -a -- ls > /dev/null 2>&1
+
+ # System-wide mode should include perf samples so test for that
+ # instead of ls
+ perf_script_branch_samples perf
+
+ err=$?
+ arm_cs_report "CoreSight system wide testing" $err
+}
+
+arm_cs_etm_snapshot_test() {
+ echo "Recording trace with snapshot mode"
+ perf record -o ${perfdata} -e cs_etm// -S -m,128K \
+ -- dd if=/dev/zero of=/dev/null > /dev/null 2>&1 &
+ PERFPID=$!
+
+ # Wait for perf program
+ sleep 1
+
+ # Send signal to snapshot trace data
+ kill -USR2 $PERFPID
+
+ # Stop perf program
+ kill $PERFPID
+ wait $PERFPID
+
+ perf_script_branch_samples dd &&
+ perf_report_instruction_samples dd
+
+ err=$?
+ arm_cs_report "CoreSight snapshot testing" $err
+}
+
+arm_cs_etm_basic_test() {
+ echo "Recording trace with '$*'"
+ perf record -o ${perfdata} "$@" -- true > /dev/null 2>&1
+
+ perf_script_branch_samples true
+
+ err=$?
+ arm_cs_report "CoreSight basic testing with '$*'" $err
+}
+
+arm_cs_etm_test_cpu_list() {
+ echo "Testing sparse CPU list: $1"
+ perf record -o ${perfdata} -e cs_etm//u -C $1 \
+ -- taskset --cpu-list $1 true > /dev/null 2>&1
+ perf_script_branch_samples true
+ err=$?
+ arm_cs_report "CoreSight sparse CPUs with '$*'" $err
+}
+
+arm_cs_etm_sparse_cpus_test() {
+ # Iterate for every ETM device
+ cpus=()
+ for dev in /sys/bus/event_source/devices/cs_etm/cpu*; do
+ # Canonicalize the path
+ dev=`readlink -f $dev`
+
+ # Find the ETM device belonging to which CPU
+ cpus+=("$(cat $dev/cpu)")
+ done
+
+ mapfile -t cpus < <(printf '%s\n' "${cpus[@]}" | sort -n)
+ total=${#cpus[@]}
+
+ # Need more than 1 to test
+ if [ $total -le 1 ]; then
+ return 0
+ fi
+
+ half=$((total / 2))
+
+ # First half
+ first_half=$(IFS=,; echo "${cpus[*]:0:$half}")
+ arm_cs_etm_test_cpu_list $first_half
+
+ # Second half
+ second_half=$(IFS=,; echo "${cpus[*]:$half}")
+ arm_cs_etm_test_cpu_list $second_half
+
+ # Odd list is the same as halves unless >= 4 CPUs
+ if [ $total -lt 4 ]; then
+ return 0
+ fi
+
+ # Odd indices
+ odd_cpus=()
+ for ((i=1; i<total; i+=2)); do
+ odd_cpus+=("${cpus[$i]}")
+ done
+ odd_list=$(IFS=,; echo "${odd_cpus[*]}")
+ arm_cs_etm_test_cpu_list $odd_list
+}
+
+arm_cs_etm_traverse_path_test
+arm_cs_etm_system_wide_test
+arm_cs_etm_snapshot_test
+
+# Test all combinations of per-thread, system-wide and normal mode with
+# and without timestamps
+arm_cs_etm_basic_test -e cs_etm/timestamp=0/u --per-thread
+arm_cs_etm_basic_test -e cs_etm/timestamp=1/u --per-thread
+arm_cs_etm_basic_test -e cs_etm/timestamp=0/u -a
+arm_cs_etm_basic_test -e cs_etm/timestamp=1/u -a
+arm_cs_etm_basic_test -e cs_etm/timestamp=0/u
+arm_cs_etm_basic_test -e cs_etm/timestamp=1/u
+
+arm_cs_etm_sparse_cpus_test
+
+exit $glb_err
diff --git a/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh b/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh
new file mode 100755
index 000000000..f3ebad596
--- /dev/null
+++ b/tools/perf/tests/shell/coresight/test_arm_coresight_disasm.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+# Check Arm CoreSight disassembly script completes without errors (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+# The disassembly script reconstructs ranges of instructions and gives these to objdump to
+# decode. objdump doesn't like ranges that go backwards, but these are a good indication
+# that decoding has gone wrong either in OpenCSD, Perf or in the range reconstruction in
+# the script. Test all 3 parts are working correctly by running the script.
+
+skip_if_no_cs_etm_event() {
+ perf list pmu | grep -q 'cs_etm//' && return 0
+
+ # cs_etm event doesn't exist
+ return 2
+}
+
+skip_if_no_cs_etm_event || exit 2
+
+# Assume an error unless we reach the very end
+set -e
+glb_err=1
+
+perfdata_dir=$(mktemp -d /tmp/__perf_test.perf.data.XXXXX)
+perfdata=${perfdata_dir}/perf.data
+file=$(mktemp /tmp/temporary_file.XXXXX)
+# Relative path works whether it's installed or running from repo
+script_path=$(dirname "$0")/../../../scripts/python/arm-cs-trace-disasm.py
+
+cleanup_files()
+{
+ set +e
+ rm -rf ${perfdata_dir}
+ rm -f ${file}
+ trap - EXIT TERM INT
+ exit $glb_err
+}
+
+trap cleanup_files EXIT TERM INT
+
+# Ranges start and end on branches, so check for some likely branch instructions
+branch_search='[[:space:]](bl|b(\.(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al))?|br|blr|ret|cbz|cbnz|tbz|tbnz|svc|eret)([[:space:]]|$)'
+
+## Test kernel ##
+if [ "$(id -u)" == 0 ] && [ -e /proc/kcore ]; then
+ echo "Testing kernel disassembly"
+ perf record -o ${perfdata} -e cs_etm//k --kcore -Se -m,64K -- touch $file > /dev/null 2>&1
+ perf script -i ${perfdata} --itrace=b -s python:${script_path} -- \
+ -d --stop-sample=2 -k ${perfdata}/kcore_dir/kcore 2> /dev/null > ${file}
+ grep -q -E ${branch_search} ${file}
+ echo "Found kernel branches"
+else
+ # Root and kcore are required for correct kernel decode due to runtime code patching
+ echo "No root or kcore, skipping kernel test"
+fi
+
+## Test user ##
+echo "Testing userspace disassembly"
+perf record -o ${perfdata} -e cs_etm//u -Se -m,64K -- touch $file > /dev/null 2>&1
+perf script -i ${perfdata} --itrace=b -s python:${script_path} -- \
+ -d --stop-sample=2 2> /dev/null > ${file}
+grep -q -E ${branch_search} ${file}
+echo "Found userspace branches"
+
+glb_err=0
diff --git a/tools/perf/tests/shell/daemon.sh b/tools/perf/tests/shell/daemon.sh
new file mode 100755
index 000000000..e5fa8d6f9
--- /dev/null
+++ b/tools/perf/tests/shell/daemon.sh
@@ -0,0 +1,538 @@
+#!/bin/bash
+# daemon operations
+# SPDX-License-Identifier: GPL-2.0
+
+check_line_first()
+{
+ local line=$1
+ local name=$2
+ local base=$3
+ local output=$4
+ local lock=$5
+ local up=$6
+
+ local line_name
+ line_name=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $2 }'`
+ local line_base
+ line_base=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $3 }'`
+ local line_output
+ line_output=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $4 }'`
+ local line_lock
+ line_lock=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $5 }'`
+ local line_up
+ line_up=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $6 }'`
+
+ if [ "${name}" != "${line_name}" ]; then
+ echo "FAILED: wrong name"
+ error=1
+ fi
+
+ if [ "${base}" != "${line_base}" ]; then
+ echo "FAILED: wrong base"
+ error=1
+ fi
+
+ if [ "${output}" != "${line_output}" ]; then
+ echo "FAILED: wrong output"
+ error=1
+ fi
+
+ if [ "${lock}" != "${line_lock}" ]; then
+ echo "FAILED: wrong lock"
+ error=1
+ fi
+
+ if [ "${up}" != "${line_up}" ]; then
+ echo "FAILED: wrong up"
+ error=1
+ fi
+}
+
+check_line_other()
+{
+ local line=$1
+ local name=$2
+ local run=$3
+ local base=$4
+ local output=$5
+ local control=$6
+ local ack=$7
+ local up=$8
+
+ local line_name
+ line_name=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $2 }'`
+ local line_run
+ line_run=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $3 }'`
+ local line_base
+ line_base=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $4 }'`
+ local line_output
+ line_output=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $5 }'`
+ local line_control
+ line_control=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $6 }'`
+ local line_ack
+ line_ack=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $7 }'`
+ local line_up
+ line_up=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $8 }'`
+
+ if [ "${name}" != "${line_name}" ]; then
+ echo "FAILED: wrong name"
+ error=1
+ fi
+
+ if [ "${run}" != "${line_run}" ]; then
+ echo "FAILED: wrong run"
+ error=1
+ fi
+
+ if [ "${base}" != "${line_base}" ]; then
+ echo "FAILED: wrong base"
+ error=1
+ fi
+
+ if [ "${output}" != "${line_output}" ]; then
+ echo "FAILED: wrong output"
+ error=1
+ fi
+
+ if [ "${control}" != "${line_control}" ]; then
+ echo "FAILED: wrong control"
+ error=1
+ fi
+
+ if [ "${ack}" != "${line_ack}" ]; then
+ echo "FAILED: wrong ack"
+ error=1
+ fi
+
+ if [ "${up}" != "${line_up}" ]; then
+ echo "FAILED: wrong up"
+ error=1
+ fi
+}
+
+daemon_exit()
+{
+ local config=$1
+
+ local line
+ line=`perf daemon --config ${config} -x: | head -1`
+ local pid
+ pid=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $1 }'`
+
+ # Reset trap handler.
+ trap - SIGINT SIGTERM
+
+ # stop daemon
+ perf daemon stop --config ${config}
+
+ # ... and wait for the pid to go away
+ tail --pid=${pid} -f /dev/null
+}
+
+daemon_start()
+{
+ local config=$1
+ local session=$2
+
+ perf daemon start --config ${config}
+
+ # Clean up daemon if interrupted.
+ trap 'echo "FAILED: Signal caught"; daemon_exit "${config}"; exit 1' SIGINT SIGTERM
+
+ # wait for the session to ping
+ local state="FAIL"
+ local retries=0
+ while [ "${state}" != "OK" ]; do
+ state=`perf daemon ping --config ${config} --session ${session} | awk '{ print $1 }'`
+ sleep 0.05
+ retries=$((${retries} +1))
+ if [ ${retries} -ge 600 ]; then
+ echo "FAILED: Timeout waiting for daemon to ping"
+ daemon_exit ${config}
+ exit 1
+ fi
+ done
+}
+
+test_list()
+{
+ echo "test daemon list"
+
+ local config
+ config=$(mktemp /tmp/perf.daemon.config.XXX)
+ local base
+ base=$(mktemp -d /tmp/perf.daemon.base.XXX)
+
+ cat <<EOF > ${config}
+[daemon]
+base=BASE
+
+[session-size]
+run = -e cpu-clock -m 1 sleep 10
+
+[session-time]
+run = -e task-clock -m 1 sleep 10
+EOF
+
+ sed -i -e "s|BASE|${base}|" ${config}
+
+ # start daemon
+ daemon_start ${config} size
+
+ # check first line
+ # pid:daemon:base:base/output:base/lock
+ local line
+ line=`perf daemon --config ${config} -x: | head -1`
+ check_line_first ${line} daemon ${base} ${base}/output ${base}/lock "0"
+
+ # check 1st session
+ # pid:size:-e cpu-clock:base/size:base/size/output:base/size/control:base/size/ack:0
+ local line
+ line=`perf daemon --config ${config} -x: | head -2 | tail -1`
+ check_line_other "${line}" size "-e cpu-clock -m 1 sleep 10" ${base}/session-size \
+ ${base}/session-size/output ${base}/session-size/control \
+ ${base}/session-size/ack "0"
+
+ # check 2nd session
+ # pid:time:-e task-clock:base/time:base/time/output:base/time/control:base/time/ack:0
+ local line
+ line=`perf daemon --config ${config} -x: | head -3 | tail -1`
+ check_line_other "${line}" time "-e task-clock -m 1 sleep 10" ${base}/session-time \
+ ${base}/session-time/output ${base}/session-time/control \
+ ${base}/session-time/ack "0"
+
+ # stop daemon
+ daemon_exit ${config}
+
+ rm -rf ${base}
+ rm -f ${config}
+}
+
+test_reconfig()
+{
+ echo "test daemon reconfig"
+
+ local config
+ config=$(mktemp /tmp/perf.daemon.config.XXX)
+ local base
+ base=$(mktemp -d /tmp/perf.daemon.base.XXX)
+
+ # prepare config
+ cat <<EOF > ${config}
+[daemon]
+base=BASE
+
+[session-size]
+run = -e cpu-clock -m 1 sleep 10
+
+[session-time]
+run = -e task-clock -m 1 sleep 10
+EOF
+
+ sed -i -e "s|BASE|${base}|" ${config}
+
+ # start daemon
+ daemon_start ${config} size
+
+ # check 2nd session
+ # pid:time:-e task-clock:base/time:base/time/output:base/time/control:base/time/ack:0
+ local line
+ line=`perf daemon --config ${config} -x: | head -3 | tail -1`
+ check_line_other "${line}" time "-e task-clock -m 1 sleep 10" ${base}/session-time \
+ ${base}/session-time/output ${base}/session-time/control ${base}/session-time/ack "0"
+ local pid
+ pid=`echo "${line}" | awk 'BEGIN { FS = ":" } ; { print $1 }'`
+
+ # prepare new config
+ local config_new=${config}.new
+ cat <<EOF > ${config_new}
+[daemon]
+base=BASE
+
+[session-size]
+run = -e cpu-clock -m 1 sleep 10
+
+[session-time]
+run = -e cpu-clock -m 1 sleep 10
+EOF
+
+ # TEST 1 - change config
+
+ sed -i -e "s|BASE|${base}|" ${config_new}
+ cp ${config_new} ${config}
+
+ # wait for old session to finish
+ tail --pid=${pid} -f /dev/null
+
+ # wait for new one to start
+ local state="FAIL"
+ while [ "${state}" != "OK" ]; do
+ state=`perf daemon ping --config ${config} --session time | awk '{ print $1 }'`
+ done
+
+ # check reconfigured 2nd session
+ # pid:time:-e task-clock:base/time:base/time/output:base/time/control:base/time/ack:0
+ local line
+ line=`perf daemon --config ${config} -x: | head -3 | tail -1`
+ check_line_other "${line}" time "-e cpu-clock -m 1 sleep 10" ${base}/session-time \
+ ${base}/session-time/output ${base}/session-time/control ${base}/session-time/ack "0"
+
+ # TEST 2 - empty config
+
+ local config_empty=${config}.empty
+ cat <<EOF > ${config_empty}
+[daemon]
+base=BASE
+EOF
+
+ # change config
+ sed -i -e "s|BASE|${base}|" ${config_empty}
+ cp ${config_empty} ${config}
+
+ # wait for sessions to finish
+ local state="OK"
+ while [ "${state}" != "FAIL" ]; do
+ state=`perf daemon ping --config ${config} --session time | awk '{ print $1 }'`
+ done
+
+ local state="OK"
+ while [ "${state}" != "FAIL" ]; do
+ state=`perf daemon ping --config ${config} --session size | awk '{ print $1 }'`
+ done
+
+ local one
+ one=`perf daemon --config ${config} -x: | wc -l`
+
+ if [ ${one} -ne "1" ]; then
+ echo "FAILED: wrong list output"
+ error=1
+ fi
+
+ # TEST 3 - config again
+
+ cp ${config_new} ${config}
+
+ # wait for size to start
+ local state="FAIL"
+ while [ "${state}" != "OK" ]; do
+ state=`perf daemon ping --config ${config} --session size | awk '{ print $1 }'`
+ done
+
+ # wait for time to start
+ local state="FAIL"
+ while [ "${state}" != "OK" ]; do
+ state=`perf daemon ping --config ${config} --session time | awk '{ print $1 }'`
+ done
+
+ # stop daemon
+ daemon_exit ${config}
+
+ rm -rf ${base}
+ rm -f ${config}
+ rm -f ${config_new}
+ rm -f ${config_empty}
+}
+
+test_stop()
+{
+ echo "test daemon stop"
+
+ local config
+ config=$(mktemp /tmp/perf.daemon.config.XXX)
+ local base
+ base=$(mktemp -d /tmp/perf.daemon.base.XXX)
+
+ # prepare config
+ cat <<EOF > ${config}
+[daemon]
+base=BASE
+
+[session-size]
+run = -e cpu-clock -m 1 sleep 10
+
+[session-time]
+run = -e task-clock -m 1 sleep 10
+EOF
+
+ sed -i -e "s|BASE|${base}|" ${config}
+
+ # start daemon
+ daemon_start ${config} size
+
+ local pid_size
+ pid_size=`perf daemon --config ${config} -x: | head -2 | tail -1 |
+ awk 'BEGIN { FS = ":" } ; { print $1 }'`
+ local pid_time
+ pid_time=`perf daemon --config ${config} -x: | head -3 | tail -1 |
+ awk 'BEGIN { FS = ":" } ; { print $1 }'`
+
+ # check that sessions are running
+ if [ ! -d "/proc/${pid_size}" ]; then
+ echo "FAILED: session size not up"
+ fi
+
+ if [ ! -d "/proc/${pid_time}" ]; then
+ echo "FAILED: session time not up"
+ fi
+
+ # stop daemon
+ daemon_exit ${config}
+
+ # check that sessions are gone
+ if [ -d "/proc/${pid_size}" ]; then
+ echo "FAILED: session size still up"
+ fi
+
+ if [ -d "/proc/${pid_time}" ]; then
+ echo "FAILED: session time still up"
+ fi
+
+ rm -rf ${base}
+ rm -f ${config}
+}
+
+test_signal()
+{
+ echo "test daemon signal"
+
+ local config
+ config=$(mktemp /tmp/perf.daemon.config.XXX)
+ local base
+ base=$(mktemp -d /tmp/perf.daemon.base.XXX)
+
+ # prepare config
+ cat <<EOF > ${config}
+[daemon]
+base=BASE
+
+[session-test]
+run = -e cpu-clock --switch-output -m 1 sleep 10
+EOF
+
+ sed -i -e "s|BASE|${base}|" ${config}
+
+ # start daemon
+ daemon_start ${config} test
+
+ # send 2 signals then exit. Do this in a loop watching the number of
+ # files to avoid races. If the loop retries more than 600 times then
+ # give up.
+ local retries=0
+ local signals=0
+ local success=0
+ while [ ${retries} -lt 600 ] && [ ${success} -eq 0 ]; do
+ local files
+ files=`ls ${base}/session-test/*perf.data* 2> /dev/null | wc -l`
+ if [ ${signals} -eq 0 ]; then
+ perf daemon signal --config ${config} --session test
+ signals=1
+ elif [ ${signals} -eq 1 ] && [ $files -ge 1 ]; then
+ perf daemon signal --config ${config}
+ signals=2
+ elif [ ${signals} -eq 2 ] && [ $files -ge 2 ]; then
+ daemon_exit ${config}
+ signals=3
+ elif [ ${signals} -eq 3 ] && [ $files -ge 3 ]; then
+ success=1
+ fi
+ retries=$((${retries} +1))
+ done
+ if [ ${success} -eq 0 ]; then
+ error=1
+ echo "FAILED: perf data no generated"
+ fi
+
+ rm -rf ${base}
+ rm -f ${config}
+}
+
+test_ping()
+{
+ echo "test daemon ping"
+
+ local config
+ config=$(mktemp /tmp/perf.daemon.config.XXX)
+ local base
+ base=$(mktemp -d /tmp/perf.daemon.base.XXX)
+
+ # prepare config
+ cat <<EOF > ${config}
+[daemon]
+base=BASE
+
+[session-size]
+run = -e cpu-clock -m 1 sleep 10
+
+[session-time]
+run = -e task-clock -m 1 sleep 10
+EOF
+
+ sed -i -e "s|BASE|${base}|" ${config}
+
+ # start daemon
+ daemon_start ${config} size
+
+ size=`perf daemon ping --config ${config} --session size | awk '{ print $1 }'`
+ type=`perf daemon ping --config ${config} --session time | awk '{ print $1 }'`
+
+ if [ ${size} != "OK" ] || [ ${type} != "OK" ]; then
+ error=1
+ echo "FAILED: daemon ping failed"
+ fi
+
+ # stop daemon
+ daemon_exit ${config}
+
+ rm -rf ${base}
+ rm -f ${config}
+}
+
+test_lock()
+{
+ echo "test daemon lock"
+
+ local config
+ config=$(mktemp /tmp/perf.daemon.config.XXX)
+ local base
+ base=$(mktemp -d /tmp/perf.daemon.base.XXX)
+
+ # prepare config
+ cat <<EOF > ${config}
+[daemon]
+base=BASE
+
+[session-size]
+run = -e cpu-clock -m 1 sleep 10
+EOF
+
+ sed -i -e "s|BASE|${base}|" ${config}
+
+ # start daemon
+ daemon_start ${config} size
+
+ # start second daemon over the same config/base
+ failed=`perf daemon start --config ${config} 2>&1 | awk '{ print $1 }'`
+
+ # check that we failed properly
+ if [ ${failed} != "failed:" ]; then
+ error=1
+ echo "FAILED: daemon lock failed"
+ fi
+
+ # stop daemon
+ daemon_exit ${config}
+
+ rm -rf ${base}
+ rm -f ${config}
+}
+
+error=0
+
+test_list
+test_reconfig
+test_stop
+test_signal
+test_ping
+test_lock
+
+exit ${error}
diff --git a/tools/perf/tests/shell/data_file_offset_diagnostics.sh b/tools/perf/tests/shell/data_file_offset_diagnostics.sh
new file mode 100755
index 000000000..389b0b847
--- /dev/null
+++ b/tools/perf/tests/shell/data_file_offset_diagnostics.sh
@@ -0,0 +1,91 @@
+#!/bin/bash
+# Test that perf report includes file offsets and event type names in diagnostic messages.
+# SPDX-License-Identifier: GPL-2.0
+#
+# The file_offset diagnostic series adds "at offset 0x...: TYPE (N)"
+# to all skip/stop/error messages. This test corrupts an event's size
+# field in a perf.data file, then verifies the resulting warning
+# includes the file offset and event type.
+
+err=0
+
+cleanup() {
+ [ -n "${perfdata}" ] && rm -f "${perfdata}" "${perfdata}.old"
+ rm -f "${corrupted}" "${stderrfile}"
+ trap - EXIT TERM INT
+}
+trap 'cleanup; exit 1' TERM INT
+trap cleanup EXIT
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
+corrupted=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
+stderrfile=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
+
+if ! perf record -o "${perfdata}" -- perf test -w noploop 2>/dev/null; then
+ echo "Skip: perf record failed"
+ cleanup
+ exit 2
+fi
+
+# Find the file offset of the first MMAP2 event via perf report -D.
+# Format: "timestamp 0xOFFSET [0xSIZE]: PERF_RECORD_MMAP2 ..."
+mmap2_line=$(perf report -D -i "${perfdata}" 2>/dev/null | grep "PERF_RECORD_MMAP2" | head -1)
+if [ -z "${mmap2_line}" ]; then
+ echo "Skip: no MMAP2 events found in perf.data"
+ cleanup
+ exit 2
+fi
+
+mmap2_offset=$(echo "${mmap2_line}" | awk '{print $2}')
+mmap2_offset_dec=$((mmap2_offset))
+
+# Copy the file and corrupt the MMAP2 event's size field.
+# perf_event_header layout: type(u32) misc(u16) size(u16)
+# Set size to 16 — below the MMAP2 minimum, which triggers
+# the "event size too small" warning.
+#
+# perf.data uses native byte order, so write the u16 in the
+# host's endianness to work on both LE and BE architectures.
+cp "${perfdata}" "${corrupted}"
+if printf '\x01\x02' | od -A n -t x2 | grep -q "0201"; then
+ # Little-endian
+ printf '\x10\x00'
+else
+ # Big-endian
+ printf '\x00\x10'
+fi | dd of="${corrupted}" bs=1 seek=$((mmap2_offset_dec + 6)) conv=notrunc 2>/dev/null
+
+perf report -i "${corrupted}" --stdio > /dev/null 2> "${stderrfile}"
+
+# Check that warnings include "at offset 0x..."
+if grep -q "at offset 0x" "${stderrfile}"; then
+ echo "File offset in diagnostics [Success]"
+else
+ echo "File offset in diagnostics [Failed: no 'at offset 0x...' found]"
+ echo " stderr was:"
+ head -5 "${stderrfile}"
+ err=1
+fi
+
+# Check that the event type name and numeric id appear: "MMAP2 (10)"
+if grep -q "MMAP2 (10)" "${stderrfile}"; then
+ echo "Event type name in diagnostics [Success]"
+else
+ echo "Event type name in diagnostics [Failed: no 'MMAP2 (10)' found]"
+ echo " stderr was:"
+ head -5 "${stderrfile}"
+ err=1
+fi
+
+# Check that the reported offset matches the actual corruption point
+if grep -q "at offset ${mmap2_offset}:" "${stderrfile}"; then
+ echo "Correct offset reported [Success]"
+else
+ echo "Correct offset reported [Failed: expected offset ${mmap2_offset}]"
+ echo " stderr was:"
+ head -5 "${stderrfile}"
+ err=1
+fi
+
+cleanup
+exit ${err}
diff --git a/tools/perf/tests/shell/data_type_profiling.sh b/tools/perf/tests/shell/data_type_profiling.sh
new file mode 100755
index 000000000..eca694600
--- /dev/null
+++ b/tools/perf/tests/shell/data_type_profiling.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+# perf data type profiling tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+# The logic below follows the same line as the annotate test, but looks for a
+# data type profiling manifestation
+
+# Values in testtypes and testprogs should match
+testtypes=("# data-type: struct Buf" "# data-type: struct buf")
+testprogs=("perf test -w code_with_type" "perf test -w datasym")
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+perfout=$(mktemp /tmp/__perf_test.perf.out.XXXXX)
+
+# Check for support of perf mem before trap handler
+perf mem record -o /dev/null -- true 2>&1 | \
+ grep -q "failed: no PMU supports the memory events" && exit 2
+
+cleanup() {
+ rm -rf "${perfdata}" "${perfout}"
+ rm -rf "${perfdata}".old
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_basic_annotate() {
+ mode=$1
+ runtime=$2
+
+ echo "${mode} ${runtime} perf annotate test"
+
+ case "x${runtime}" in
+ "xRust")
+ if ! perf check feature -q rust
+ then
+ echo "Skip: code_with_type workload not built in 'perf test'"
+ return
+ fi
+ index=0 ;;
+
+ "xC")
+ index=1 ;;
+ esac
+
+ if [ "x${mode}" == "xBasic" ]
+ then
+ perf mem record -o "${perfdata}" ${testprogs[$index]} 2> /dev/null
+ else
+ perf mem record -o - ${testprogs[$index]} 2> /dev/null > "${perfdata}"
+ fi
+ if [ "x$?" != "x0" ]
+ then
+ echo "${mode} annotate [Failed: perf record]"
+ err=1
+ return
+ fi
+
+ # Generate the annotated output file
+ if [ "x${mode}" == "xBasic" ]
+ then
+ perf annotate --code-with-type -i "${perfdata}" --stdio --percent-limit 1 2> /dev/null > "${perfout}"
+ else
+ perf annotate --code-with-type -i - --stdio 2> /dev/null --percent-limit 1 < "${perfdata}" > "${perfout}"
+ fi
+
+ # check if it has the target data type
+ if ! grep -q "${testtypes[$index]}" "${perfout}"
+ then
+ echo "${mode} annotate [Failed: missing target data type]"
+ cat "${perfout}"
+ err=1
+ return
+ fi
+ echo "${mode} annotate test [Success]"
+}
+
+test_basic_annotate Basic Rust
+test_basic_annotate Pipe Rust
+test_basic_annotate Basic C
+test_basic_annotate Pipe C
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/data_validation.sh b/tools/perf/tests/shell/data_validation.sh
new file mode 100755
index 000000000..bf16b2f2b
--- /dev/null
+++ b/tools/perf/tests/shell/data_validation.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+# Test that perf report handles truncated perf.data gracefully (no crash, no segfault — clean error exit).
+# SPDX-License-Identifier: GPL-2.0
+#
+# Exercises the bounds checking and minimum-size validation added
+# by the perf-data-validation hardening series.
+
+err=0
+
+cleanup() {
+ [ -n "${perfdata}" ] && rm -f "${perfdata}" "${perfdata}.old"
+ rm -f "${truncated}" "${stderrfile}"
+ trap - EXIT TERM INT
+}
+trap 'cleanup; exit 1' TERM INT
+trap cleanup EXIT
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
+truncated=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
+stderrfile=$(mktemp /tmp/__perf_test.perf.data.XXXXX) || exit 2
+
+# Record a simple workload
+if ! perf record -o "${perfdata}" -- perf test -w noploop 2>/dev/null; then
+ echo "Skip: perf record failed"
+ cleanup
+ exit 2
+fi
+
+file_size=$(wc -c < "${perfdata}")
+if [ "${file_size}" -lt 512 ]; then
+ echo "Skip: perf.data too small (${file_size} bytes)"
+ cleanup
+ exit 2
+fi
+
+# Test truncation at various offsets that exercise different
+# parsing stages:
+# 8 — file header magic only, no attrs or data
+# 64 — partial file header (attr section incomplete)
+# 256 — into the first events (partial event headers)
+# 75% — mid-stream truncation (partial event data)
+for cut_at in 8 64 256 $((file_size * 3 / 4)); do
+ if [ "${cut_at}" -ge "${file_size}" ]; then
+ continue
+ fi
+ dd if="${perfdata}" of="${truncated}" bs="${cut_at}" count=1 2>/dev/null
+
+ # perf report should exit with an error, not crash.
+ # Capture stderr to detect sanitizer violations.
+ perf report -i "${truncated}" --stdio > /dev/null 2> "${stderrfile}"
+ exit_code=$?
+
+ # A truncated file should never parse successfully
+ if [ ${exit_code} -eq 0 ]; then
+ echo "FAIL: perf report exited 0 (success) on ${cut_at}-byte truncated file — expected an error"
+ err=1
+ continue
+ fi
+
+ # Detect sanitizer violations — ASAN/MSAN/TSAN/UBSAN exit
+ # with code 1 by default, which would otherwise look like a
+ # clean error exit. Check stderr for their markers.
+ if grep -qE "^(==[0-9]+==ERROR:|SUMMARY: [A-Za-z]*Sanitizer)" "${stderrfile}" 2>/dev/null; then
+ sanitizer=$(grep -oE "(Address|Memory|Thread|UndefinedBehavior)Sanitizer" "${stderrfile}" | head -1)
+ echo "FAIL: perf report triggered ${sanitizer:-sanitizer} on ${cut_at}-byte truncated file"
+ err=1
+ continue
+ fi
+
+ # Detect crash signals portably — signal numbers differ
+ # across architectures (e.g. SIGBUS is 7 on x86/ARM but
+ # 10 on MIPS/SPARC). Use kill -l to map the number to a
+ # name on the running system.
+ if [ ${exit_code} -gt 128 ] && [ ${exit_code} -lt 200 ]; then
+ sig_name=$(kill -l $((exit_code - 128)) 2>/dev/null)
+ case ${sig_name} in
+ KILL|ILL|ABRT|BUS|FPE|SEGV|TRAP|SYS)
+ echo "FAIL: perf report crashed (SIG${sig_name}) on ${cut_at}-byte truncated file"
+ err=1
+ ;;
+ esac
+ fi
+done
+
+cleanup
+exit ${err}
diff --git a/tools/perf/tests/shell/diff.sh b/tools/perf/tests/shell/diff.sh
new file mode 100755
index 000000000..fe05fdebc
--- /dev/null
+++ b/tools/perf/tests/shell/diff.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# perf diff tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata1=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+perfdata2=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+perfdata3=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+testprog="perf test -w thloop"
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+testsym="test_loop"
+
+skip_test_missing_symbol ${testsym}
+
+cleanup() {
+ rm -rf "${perfdata1}"
+ rm -rf "${perfdata1}".old
+ rm -rf "${perfdata2}"
+ rm -rf "${perfdata2}".old
+ rm -rf "${perfdata3}"
+ rm -rf "${perfdata3}".old
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+make_data() {
+ file="$1"
+ if ! perf record -o "${file}" ${testprog} 2> /dev/null
+ then
+ echo "Workload record [Failed record]" >&2
+ echo 1
+ return
+ fi
+ if ! perf report -i "${file}" -q | grep -q "${testsym}"
+ then
+ echo "Workload record [Failed missing output]" >&2
+ echo 1
+ return
+ fi
+ echo 0
+}
+
+test_two_files() {
+ echo "Basic two file diff test"
+ err=$(make_data "${perfdata1}")
+ if [ "$err" != 0 ]
+ then
+ return
+ fi
+ err=$(make_data "${perfdata2}")
+ if [ "$err" != 0 ]
+ then
+ return
+ fi
+
+ if ! perf diff "${perfdata1}" "${perfdata2}" | grep -q "${testsym}"
+ then
+ echo "Basic two file diff test [Failed diff]"
+ err=1
+ return
+ fi
+ echo "Basic two file diff test [Success]"
+}
+
+test_three_files() {
+ echo "Basic three file diff test"
+ err=$(make_data "${perfdata1}")
+ if [ "$err" != 0 ]
+ then
+ return
+ fi
+ err=$(make_data "${perfdata2}")
+ if [ "$err" != 0 ]
+ then
+ return
+ fi
+ err=$(make_data "${perfdata3}")
+ if [ $err != 0 ]
+ then
+ return
+ fi
+
+ if ! perf diff "${perfdata1}" "${perfdata2}" "${perfdata3}" | grep -q "${testsym}"
+ then
+ echo "Basic three file diff test [Failed diff]"
+ err=1
+ return
+ fi
+ echo "Basic three file diff test [Success]"
+}
+
+test_two_files
+test_three_files
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/drm_pmu.sh b/tools/perf/tests/shell/drm_pmu.sh
new file mode 100755
index 000000000..e629fe0e8
--- /dev/null
+++ b/tools/perf/tests/shell/drm_pmu.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+# DRM PMU
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+output=$(mktemp /tmp/perf.drm_pmu.XXXXXX.txt)
+
+cleanup() {
+ rm -f "${output}"
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+# Array to store file descriptors and device names
+declare -A device_fds
+
+# Open all devices and store file descriptors. Opening the device will create a
+# /proc/$$/fdinfo file containing the DRM statistics.
+fd_count=3 # Start with file descriptor 3
+for device in /dev/dri/*
+do
+ if [[ ! -c "$device" ]]
+ then
+ continue
+ fi
+ major=$(stat -c "%Hr" "$device")
+ if [[ "$major" != 226 ]]
+ then
+ continue
+ fi
+ echo "Opening $device"
+ eval "exec $fd_count<\"$device\""
+ echo "fdinfo for: $device (FD: $fd_count)"
+ cat "/proc/$$/fdinfo/$fd_count"
+ echo
+ device_fds["$device"]="$fd_count"
+ fd_count=$((fd_count + 1))
+done
+
+if [[ ${#device_fds[@]} -eq 0 ]]
+then
+ echo "No DRM devices found [Skip]"
+ cleanup
+ exit 2
+fi
+
+# For each DRM event
+err=0
+for p in $(perf list --raw-dump drm-)
+do
+ echo -n "Testing perf stat of $p. "
+ perf stat -e "$p" --pid=$$ true > "$output" 2>&1
+ if ! grep -q "$p" "$output"
+ then
+ echo "Missing DRM event in: [Failed]"
+ cat "$output"
+ err=1
+ else
+ echo "[OK]"
+ fi
+done
+
+# Close all file descriptors
+for fd in "${device_fds[@]}"; do
+ eval "exec $fd<&-"
+done
+
+# Finished
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/evlist.sh b/tools/perf/tests/shell/evlist.sh
new file mode 100755
index 000000000..8a22f4171
--- /dev/null
+++ b/tools/perf/tests/shell/evlist.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+# perf evlist tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+
+cleanup() {
+ rm -f "${perfdata}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_evlist_simple() {
+ echo "Simple evlist test"
+ if ! perf record -e cpu-clock -o "${perfdata}" true 2> /dev/null
+ then
+ echo "Simple evlist [Failed record]"
+ err=1
+ return
+ fi
+ if ! perf evlist -i "${perfdata}" | grep -q "cpu-clock"
+ then
+ echo "Simple evlist [Failed to list event]"
+ err=1
+ return
+ fi
+ echo "Simple evlist test [Success]"
+}
+
+test_evlist_group() {
+ echo "Group evlist test"
+ if ! perf record -e "{cpu-clock,task-clock}" -o "${perfdata}" \
+ -- perf test -w noploop 2> /dev/null
+ then
+ echo "Group evlist [Skipped event group recording failed]"
+ return
+ fi
+
+ if ! perf evlist -i "${perfdata}" -g | grep -q "{.*cpu-clock.*,.*task-clock.*}"
+ then
+ echo "Group evlist [Failed to list event group]"
+ err=1
+ return
+ fi
+ echo "Group evlist test [Success]"
+}
+
+test_evlist_verbose() {
+ echo "Event configuration evlist test"
+ if ! perf record -e cycles -o "${perfdata}" true 2> /dev/null
+ then
+ echo "Event configuration evlist [Failed record]"
+ err=1
+ return
+ fi
+
+ if ! perf evlist -i "${perfdata}" -v | grep -q "config:"
+ then
+ echo "Event configuration evlist [Failed to list verbose info]"
+ err=1
+ return
+ fi
+ echo "Event configuration evlist test [Success]"
+}
+
+test_evlist_simple
+test_evlist_group
+test_evlist_verbose
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/ftrace.sh b/tools/perf/tests/shell/ftrace.sh
new file mode 100755
index 000000000..9f6e590f6
--- /dev/null
+++ b/tools/perf/tests/shell/ftrace.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+# perf ftrace tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+# perf ftrace commands only works for root
+if [ "$(id -u)" != 0 ]; then
+ echo "perf ftrace test [Skipped: no permission]"
+ exit 2
+fi
+
+output=$(mktemp /tmp/__perf_test.ftrace.XXXXXX)
+
+cleanup() {
+ rm -f "${output}"
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+# this will be set in test_ftrace_trace()
+target_function=
+
+test_ftrace_list() {
+ echo "perf ftrace list test"
+ perf ftrace -F > "${output}"
+ # this will be used in test_ftrace_trace()
+ sleep_functions=$(grep 'sys_.*sleep$' "${output}")
+ echo "syscalls for sleep:"
+ echo "${sleep_functions}"
+ echo "perf ftrace list test [Success]"
+}
+
+test_ftrace_trace() {
+ echo "perf ftrace trace test"
+ perf ftrace trace --graph-opts depth=5 sleep 0.1 > "${output}"
+ # it should have some function name contains 'sleep'
+ grep "^#" "${output}"
+ grep -F 'sleep()' "${output}"
+ # find actual syscall function name
+ for FN in ${sleep_functions}; do
+ if grep -q "${FN}" "${output}"; then
+ target_function="${FN}"
+ echo "perf ftrace trace test [Success]"
+ return
+ fi
+ done
+
+ echo "perf ftrace trace test [Failure: sleep syscall not found]"
+ exit 1
+}
+
+test_ftrace_latency() {
+ echo "perf ftrace latency test"
+ echo "target function: ${target_function}"
+ perf ftrace latency -T "${target_function}" sleep 0.1 > "${output}"
+ grep "^#" "${output}"
+ grep "###" "${output}"
+ echo "perf ftrace latency test [Success]"
+}
+
+test_ftrace_profile() {
+ echo "perf ftrace profile test"
+ perf ftrace profile --graph-opts depth=5 sleep 0.1 > "${output}"
+ grep ^# "${output}"
+ time_re="[[:space:]]+1[[:digit:]]{5}\.[[:digit:]]{3}"
+ # 100283.000 100283.000 100283.000 1 __x64_sys_clock_nanosleep
+ # Check for one *sys_*nanosleep line with a Count of just 1 that takes a bit more than 0.1 seconds
+ # Strip the _x64_ part to work with other architectures, strip the clock part to support
+ # C libraries that use the nanosleep syscall instead of clock_nanosleep
+ grep -E "^${time_re}${time_re}${time_re}[[:space:]]+1[[:space:]]+.*sys_.*nanosleep" "${output}"
+ echo "perf ftrace profile test [Success]"
+}
+
+test_ftrace_list
+test_ftrace_trace
+test_ftrace_latency
+test_ftrace_profile
+
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/header.sh b/tools/perf/tests/shell/header.sh
new file mode 100755
index 000000000..e1628ac0a
--- /dev/null
+++ b/tools/perf/tests/shell/header.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+# perf header tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test_header.perf.data.XXXXX)
+script_output=$(mktemp /tmp/__perf_test_header.perf.data.XXXXX.script)
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${perfdata}".old
+ rm -f "${script_output}"
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+check_header_output() {
+ declare -a fields=(
+ "captured"
+ "hostname"
+ "os release"
+ "arch"
+ "cpuid"
+ "nrcpus"
+ "event"
+ "cmdline"
+ "perf version"
+ "sibling (cores|dies|threads)"
+ "sibling threads"
+ "total memory"
+ )
+ for i in "${fields[@]}"
+ do
+ if ! grep -q -E "$i" "${script_output}"
+ then
+ echo "Failed to find expected $i in output"
+ err=1
+ fi
+ done
+}
+
+test_file() {
+ echo "Test perf header file"
+
+ perf record -o "${perfdata}" -- perf test -w noploop
+ perf report --header-only -I -i "${perfdata}" > "${script_output}"
+ check_header_output
+
+ echo "Test perf header file [Done]"
+}
+
+test_pipe() {
+ echo "Test perf header pipe"
+
+ perf record -o - -- perf test -w noploop | perf report --header-only -I -i - > "${script_output}"
+ check_header_output
+
+ echo "Test perf header pipe [Done]"
+}
+
+test_file
+test_pipe
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/inject-callchain.sh b/tools/perf/tests/shell/inject-callchain.sh
new file mode 100755
index 000000000..a1cba8010
--- /dev/null
+++ b/tools/perf/tests/shell/inject-callchain.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+# perf inject to convert DWARF callchains to regular ones
+# SPDX-License-Identifier: GPL-2.0
+
+if ! perf check feature -q dwarf; then
+ echo "SKIP: DWARF support is not available"
+ exit 2
+fi
+
+TESTDATA=$(mktemp /tmp/perf-test.XXXXXX)
+
+err=0
+
+cleanup()
+{
+ trap - EXIT TERM INT
+ rm -f ${TESTDATA}*
+}
+
+trap_cleanup()
+{
+ cleanup
+ exit 1
+}
+
+trap trap_cleanup EXIT TERM INT
+
+echo "recording data with DWARF callchain"
+perf record -F 999 --call-graph dwarf -o "${TESTDATA}" -- perf test -w noploop
+
+echo "convert DWARF callchain using perf inject"
+perf inject -i "${TESTDATA}" --convert-callchain -o "${TESTDATA}.new"
+
+perf report -i "${TESTDATA}" --no-children -q --percent-limit=1 > ${TESTDATA}.out
+perf report -i "${TESTDATA}.new" --no-children -q --percent-limit=1 > ${TESTDATA}.new.out
+
+echo "compare the both result excluding inlined functions"
+if diff -u "${TESTDATA}.out" "${TESTDATA}.new.out" | grep "^- " | grep -qv "(inlined)"; then
+ echo "Found some differences"
+ diff -u "${TESTDATA}.out" "${TESTDATA}.new.out"
+ err=1
+fi
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/inject_aslr.sh b/tools/perf/tests/shell/inject_aslr.sh
new file mode 100755
index 000000000..d83c2591d
--- /dev/null
+++ b/tools/perf/tests/shell/inject_aslr.sh
@@ -0,0 +1,539 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf inject --aslr test
+
+set -e
+set -o pipefail
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+# Create global temp directory
+temp_dir=$(mktemp -d /tmp/perf-test-aslr.XXXXXXXXXX)
+
+prog="perf test -w noploop"
+[ "$(uname -m)" = "s390x" ] && prog="$prog 3"
+err=0
+kprog="dd if=/dev/urandom of=/dev/null bs=1M count=50"
+
+cleanup() {
+ local exit_code=${1:-$?}
+ trap - EXIT TERM INT
+ if [ "${exit_code}" -ne 0 ] || [ "${err}" -ne 0 ]; then
+ echo "Test failed! Preserving temp directory: ${temp_dir}"
+ return
+ fi
+ # Check if temp_dir is set and looks sane before removing
+ if [[ "${temp_dir}" =~ ^/tmp/perf-test-aslr\. ]]; then
+ rm -rf "${temp_dir}"
+ fi
+}
+
+trap_cleanup() {
+ local exit_code=$?
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup ${exit_code}
+ exit ${exit_code}
+}
+trap trap_cleanup EXIT TERM INT
+
+get_noploop_addr() {
+ local file=$1
+ perf script -i "$file" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<=NF; i++) {
+ if ($i ~ /noploop\+/) {
+ if (!found) {
+ print $(i-1)
+ found=1
+ }
+ }
+ }
+ }'
+}
+
+test_basic_aslr() {
+ echo "Test basic ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.basic.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.basic.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -v --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Basic ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Basic ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Basic ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Basic ASLR test [Success]"
+ fi
+}
+
+test_pipe_aslr() {
+ echo "Test pipe mode ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe.XXXXXX")
+
+ # Use tee to save the original pipe data for comparison
+ perf record -e task-clock:u -o - ${prog} | tee "${data}" | perf inject --aslr -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Pipe ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Pipe ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Pipe ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Pipe ASLR test [Success]"
+ fi
+}
+
+test_callchain_aslr() {
+ echo "Test Callchain ASLR remapping"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.callchain.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.callchain.XXXXXX")
+
+ perf record -g -e task-clock:u -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ orig_addr=$(get_noploop_addr "${data}")
+ new_addr=$(get_noploop_addr "${data2}")
+
+ echo "Callchain ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Callchain ASLR test [Failed - no noploop samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ if perf evlist -v -i "${data}" | grep -q 'sample_type:.*STACK_USER'; then
+ echo "Dropping stack user sample as possible ASLR leak"
+ echo "Call-graph dwarf not supported with 'perf inject --aslr'"
+ echo "Callchain ASLR test [Skip]"
+ else
+ echo "Callchain ASLR test [Failed - could not find remapped address]"
+ err=1
+ fi
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Callchain ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ # Extract callchain addresses (indented lines starting with hex addresses)
+ orig_callchain=$(perf script -i "${data}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+ new_callchain=$(perf script -i "${data2}" | awk '/^[[:space:]]+[0-9a-f]+/ {print $1}')
+
+ if [ -z "$orig_callchain" ]; then
+ echo "Callchain ASLR test [Failed - no callchain samples in original file]"
+ err=1
+ elif [ -z "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain data was dropped]"
+ err=1
+ elif [ "$orig_callchain" = "$new_callchain" ]; then
+ echo "Callchain ASLR test [Failed - callchain addresses were not remapped]"
+ err=1
+ else
+ echo "Callchain ASLR test [Success]"
+ fi
+ fi
+}
+
+test_report_aslr() {
+ echo "Test perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${data}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${data}" -o "${data2}"
+
+ local report1="${temp_dir}/report1_basic"
+ local report2="${temp_dir}/report2_basic"
+ local report1_clean="${temp_dir}/report1_basic.clean"
+ local report2_clean="${temp_dir}/report2_basic.clean"
+ local diff_file="${temp_dir}/diff_basic"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_report_aslr() {
+ echo "Test pipe mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_report.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.pipe_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ # Use tee to save the original pipe data, then process it with inject -b
+ perf record -e task-clock:u -o - ${prog} | \
+ tee "${data}" | \
+ perf inject -b --aslr -o "${data2}"
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1_pipe"
+ local report2="${temp_dir}/report2_pipe"
+ local report1_clean="${temp_dir}/report1_pipe.clean"
+ local report2_clean="${temp_dir}/report2_pipe.clean"
+ local diff_file="${temp_dir}/diff_pipe"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${data2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Report ASLR test [Success]"
+ fi
+}
+
+test_pipe_out_report_aslr() {
+ echo "Test pipe output mode perf report consistency"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.pipe_out_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+
+ perf record -e task-clock:u -o "${data}" ${prog}
+ perf inject -b -i "${data}" -o "${data_clean}"
+
+ local report1="${temp_dir}/report1_pipe_out"
+ local report2="${temp_dir}/report2_pipe_out"
+ local report1_clean="${temp_dir}/report1_pipe_out.clean"
+ local report2_clean="${temp_dir}/report2_pipe_out.clean"
+ local diff_file="${temp_dir}/diff_pipe_out"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf inject -b --aslr -i "${data}" -o - | perf report -i - --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Pipe Output Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Pipe Output Report ASLR test [Success]"
+ fi
+}
+
+test_dropped_samples() {
+ echo "Test dropped samples (phys-data)"
+ local data
+ data=$(mktemp "${temp_dir}/perf.data.dropped.XXXXXX")
+ local data2
+ data2=$(mktemp "${temp_dir}/perf.data2.dropped.XXXXXX")
+
+ # Check if --phys-data is supported by recording a short run
+ if ! perf record -e task-clock:u --phys-data -o "${data}" -- sleep 0.1 > /dev/null 2>&1; then
+ echo "Skipping dropped samples test as --phys-data is not supported"
+ return
+ fi
+
+ perf record -e task-clock:u --phys-data -o "${data}" ${prog}
+ perf inject --aslr -i "${data}" -o "${data2}"
+
+ # Verify that the original file actually contained samples!
+ orig_samples=$(perf script -i "${data}" | wc -l)
+ if [ "$orig_samples" -eq 0 ]; then
+ echo "Dropped samples test [Failed - no samples in original file]"
+ err=1
+ else
+ # Verify that samples are dropped.
+ samples_count=$(perf script -i "${data2}" | wc -l)
+
+ if [ "$samples_count" -gt 0 ]; then
+ echo "Dropped samples test [Failed - samples were not dropped]"
+ err=1
+ else
+ echo "Dropped samples test [Success]"
+ fi
+ fi
+}
+
+test_kernel_aslr() {
+ echo "Test kernel ASLR remapping"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel ASLR test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel ASLR test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ perf inject -v --aslr -i "${kdata}" -o "${kdata2}"
+
+ # Check if kernel addresses are remapped.
+ # Find the field that ends with :k: (the event name) and take the next field!
+ orig_addr=$(perf script -i "${kdata}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+ new_addr=$(perf script -i "${kdata2}" | awk '
+ BEGIN { found=0 }
+ {
+ for (i=1; i<NF; i++) {
+ if ($i ~ /:[k]+:?$/) {
+ if (!found) {
+ print $(i+1)
+ found=1
+ }
+ }
+ }
+ }')
+
+ echo "Kernel ASLR: orig_addr=$orig_addr, new_addr=$new_addr"
+
+ if [ -z "$orig_addr" ]; then
+ echo "Kernel ASLR test [Failed - no kernel samples in original file]"
+ err=1
+ elif [ -z "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - could not find remapped address]"
+ err=1
+ elif [ "$orig_addr" = "$new_addr" ]; then
+ echo "Kernel ASLR test [Failed - addresses are not remapped]"
+ err=1
+ else
+ echo "Kernel ASLR test [Success]"
+ fi
+}
+
+test_kernel_report_aslr() {
+ echo "Test kernel perf report consistency"
+ local kdata
+ kdata=$(mktemp "${temp_dir}/perf.data.kernel_report.XXXXXX")
+ local kdata2
+ kdata2=$(mktemp "${temp_dir}/perf.data2.kernel_report.XXXXXX")
+ local data_clean
+ data_clean=$(mktemp "${temp_dir}/perf.data.clean.XXXXXX")
+ local log_file
+ log_file=$(mktemp "${temp_dir}/kernel_report_record.log.XXXXXX")
+
+ # Try to record kernel samples
+ if ! perf record -e task-clock:k -o "${kdata}" ${kprog} > "${log_file}" 2>&1; then
+ echo "Skipping kernel report test as recording failed (maybe no permissions)"
+ return
+ fi
+
+ # Check for warning about kernel map restriction
+ if grep -q "Couldn't record kernel reference relocation symbol" "${log_file}"; then
+ echo "Skipping kernel report test as kernel map could not be recorded (permissions restricted)"
+ return
+ fi
+
+ # Use -b to inject build-ids and force ordered events processing in both
+ perf inject -b -i "${kdata}" -o "${data_clean}"
+ perf inject -v -b --aslr -i "${kdata}" -o "${kdata2}"
+
+ local report1="${temp_dir}/report_kernel1"
+ local report2="${temp_dir}/report_kernel2"
+ local report1_clean="${temp_dir}/report_kernel1.clean"
+ local report2_clean="${temp_dir}/report_kernel2.clean"
+
+ perf report -i "${data_clean}" --stdio > "${report1}"
+ perf report -i "${kdata2}" --stdio > "${report2}"
+
+ # Strip headers and compare lines with percentages
+ grep '%' "${report1}" | grep -v '^#' > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' > "${report2_clean}" || true
+
+ # Normalize kernel DSOs and addresses in clean reports
+ # This allows kernel modules to be either a module or kernel.kallsyms
+ local report1_norm="${temp_dir}/report_kernel1.norm"
+ local report2_norm="${temp_dir}/report_kernel2.norm"
+ local diff_file="${temp_dir}/diff_kernel"
+
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report1_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report1_norm}" || true
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' "${report2_clean}" | \
+ awk '{gsub(/\[[a-zA-Z0-9_.-]{2,}\](\.[a-zA-Z0-9_]+)?/, "[kernel]", $0); print}' | \
+ sort > "${report2_norm}" || true
+
+ diff -u -w "${report1_norm}" "${report2_norm}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_norm}" ]; then
+ echo "Kernel Report ASLR test [Failed - no samples captured]"
+ err=1
+ elif [ -s "${diff_file}" ]; then
+ echo "Kernel Report ASLR test [Failed - reports differ]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ else
+ echo "Kernel Report ASLR test [Success]"
+ fi
+}
+
+test_regs_stripping() {
+ echo "Test user register stripping"
+ local rdata="${temp_dir}/perf.data.regs"
+ local rdata2="${temp_dir}/perf.data.regs.injected"
+ local rdata_clean="${temp_dir}/perf.data.regs.clean"
+
+ if ! perf record -e cycles:u --user-regs -o "${rdata}" ${prog} > /dev/null 2>&1; then
+ echo "Skipping user registers test as recording failed (unsupported flag/platform)"
+ return
+ fi
+
+ perf inject -b -i "${rdata}" -o "${rdata_clean}"
+ perf inject -v -b --aslr -i "${rdata}" -o "${rdata2}"
+
+ local report1="${temp_dir}/report_regs1"
+ local report2="${temp_dir}/report_regs2"
+ local report1_clean="${temp_dir}/report_regs1.clean"
+ local report2_clean="${temp_dir}/report_regs2.clean"
+ local diff_file="${temp_dir}/diff_regs"
+
+ perf report -i "${rdata_clean}" --stdio > "${report1}" 2>/dev/null || true
+ perf report -i "${rdata2}" --stdio > "${report2}" 2>/dev/null || true
+
+ grep '%' "${report1}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report1_clean}" || true
+ grep '%' "${report2}" | grep -v '^#' | \
+ grep -v -E '0x[0-9a-f]{8,}|0000000000000000' | \
+ sort > "${report2_clean}" || true
+
+ diff -u -w "${report1_clean}" "${report2_clean}" > "${diff_file}" || true
+
+ if [ ! -s "${report1_clean}" ]; then
+ echo "User registers stripping test [Failed - profile trace starved/empty]"
+ err=1
+ return
+ elif [ -s "${diff_file}" ]; then
+ echo "User registers stripping test [Failed - report parsing differs]"
+ echo "Showing first 20 lines of diff:"
+ head -n 20 "${diff_file}"
+ err=1
+ return
+ fi
+
+ local script_dump="${temp_dir}/script_regs_dump"
+ perf script -D -i "${rdata2}" > "${script_dump}" 2>/dev/null || true
+ if grep -q "user regs:" "${script_dump}"; then
+ echo "User registers stripping test [Failed - register dumps still present]"
+ err=1
+ else
+ echo "User registers stripping test [Success]"
+ fi
+}
+
+test_basic_aslr
+test_pipe_aslr
+test_callchain_aslr
+test_report_aslr
+test_pipe_report_aslr
+test_pipe_out_report_aslr
+test_dropped_samples
+case "$(uname -m)" in
+ aarch64*|arm*)
+ echo "Skipping kernel ASLR tests on ARM"
+ ;;
+ *)
+ test_kernel_aslr
+ test_kernel_report_aslr
+ ;;
+esac
+
+test_regs_stripping
+
+cleanup ${err}
+exit $err
diff --git a/tools/perf/tests/shell/jitdump-python.sh b/tools/perf/tests/shell/jitdump-python.sh
new file mode 100755
index 000000000..05aaa3bd9
--- /dev/null
+++ b/tools/perf/tests/shell/jitdump-python.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# python profiling with jitdump
+# SPDX-License-Identifier: GPL-2.0
+
+SHELLDIR=$(dirname $0)
+# shellcheck source=lib/setup_python.sh
+. "${SHELLDIR}"/lib/setup_python.sh
+
+OUTPUT=$(${PYTHON} -Xperf_jit -c 'import os, sys; print(os.getpid(), sys.is_stack_trampoline_active())' 2> /dev/null)
+PID=${OUTPUT% *}
+HAS_PERF_JIT=${OUTPUT#* }
+
+rm -f /tmp/jit-${PID}.dump 2> /dev/null
+if [ "${HAS_PERF_JIT}" != "True" ]; then
+ echo "SKIP: python JIT dump is not available"
+ exit 2
+fi
+
+PERF_DATA_DIR=$(mktemp -d /tmp/__perf_test.perf.data.dir.XXXXXX)
+PERF_DATA="${PERF_DATA_DIR}/perf.data"
+
+cleanup() {
+ echo "Cleaning up files..."
+ rm -rf ${PERF_DATA_DIR} 2> /dev/null
+ for p in ${ALL_PIDS}; do
+ rm -f /tmp/jit-${p}.dump /tmp/jitted-${p}-*.so 2> /dev/null
+ done
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected termination"
+ cleanup
+ exit 1
+}
+
+trap trap_cleanup EXIT TERM INT
+
+ALL_PIDS=""
+NUM=0
+for iterations in 1000000 10000000 50000000 100000000; do
+ echo "Running with $iterations iterations..."
+ rm -f "${PERF_DATA}.pid"
+ cat <<EOF | perf record -k 1 -g --call-graph dwarf -o "${PERF_DATA}" -- ${PYTHON} -Xperf_jit
+import os
+with open("${PERF_DATA}.pid", "w") as f:
+ f.write(str(os.getpid()))
+
+def foo(n):
+ result = 0
+ for _ in range(n):
+ result += 1
+ return result
+
+def bar(n):
+ foo(n)
+
+def baz(n):
+ bar(n)
+
+if __name__ == "__main__":
+ baz($iterations)
+EOF
+
+ if [ -f "${PERF_DATA}.pid" ]; then
+ REAL_PID=$(cat "${PERF_DATA}.pid")
+ ALL_PIDS="${ALL_PIDS} ${REAL_PID}"
+ fi
+
+ # extract PID of the target process from the data
+ PID=$(perf report -i "${PERF_DATA}" --stdio -F pid -q -g none | \
+ cut -d: -f1 -s | sort -u | head -n 1 | tr -d ' ')
+ if [ -z "${PID}" ]; then
+ echo "Failed to get PID, retrying..."
+ continue
+ fi
+ ALL_PIDS="${ALL_PIDS} ${PID}"
+
+ echo "Generate JIT-ed DSOs using perf inject"
+ DEBUGINFOD_URLS='' perf inject -i "${PERF_DATA}" -j -o "${PERF_DATA}.jit"
+
+ echo "Add JIT-ed DSOs to the build-ID cache"
+ for F in /tmp/jitted-${PID}-*.so; do
+ perf buildid-cache -a "${F}"
+ done
+
+ echo "Check the symbol containing the function/module name"
+ NUM=$(perf report -i "${PERF_DATA}.jit" -s sym --stdio | grep -cE 'py::(foo|bar|baz):<stdin>')
+
+ echo "Remove JIT-ed DSOs from the build-ID cache"
+ for F in /tmp/jitted-${PID}-*.so; do
+ perf buildid-cache -r "${F}"
+ done
+ rm -f /tmp/jitted-${PID}-*.so /tmp/jit-${PID}.dump 2>/dev/null
+
+ if [ "${NUM}" -gt 0 ]; then
+ echo "Success: found ${NUM} matching lines"
+ break
+ fi
+ echo "No matching lines found, retrying with more iterations..."
+done
+
+cleanup
+
+if [ "${NUM}" -eq 0 ]; then
+ exit 1
+fi
diff --git a/tools/perf/tests/shell/kallsyms.sh b/tools/perf/tests/shell/kallsyms.sh
new file mode 100755
index 000000000..d0eb99753
--- /dev/null
+++ b/tools/perf/tests/shell/kallsyms.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+# perf kallsyms tests
+# SPDX-License-Identifier: GPL-2.0
+
+err=0
+
+test_kallsyms() {
+ echo "Basic perf kallsyms test"
+
+ # Check if /proc/kallsyms is readable
+ if [ ! -r /proc/kallsyms ]; then
+ echo "Basic perf kallsyms test [Skipped: /proc/kallsyms not readable]"
+ err=2
+ return
+ fi
+
+ # Use a symbol that is definitely a function and present in all kernels, e.g. schedule
+ symbol="schedule"
+
+ # Run perf kallsyms
+ # It prints "address symbol_name"
+ output=$(perf kallsyms $symbol 2>&1)
+ ret=$?
+
+ if [ $ret -ne 0 ] || [ -z "$output" ]; then
+ # If empty or failed, it might be due to permissions (kptr_restrict)
+ # Check if we can grep the symbol from /proc/kallsyms directly
+ if grep -q "$symbol" /proc/kallsyms 2>/dev/null; then
+ # If it's in /proc/kallsyms but perf kallsyms returned empty/error,
+ # it likely means perf couldn't parse it or access it correctly (e.g. kptr_restrict=2).
+ echo "Basic perf kallsyms test [Skipped: $symbol found in /proc/kallsyms but perf kallsyms failed (output: '$output')]"
+ err=2
+ return
+ else
+ echo "Basic perf kallsyms test [Skipped: $symbol not found in /proc/kallsyms]"
+ err=2
+ return
+ fi
+ fi
+
+ if echo "$output" | grep -q "not found"; then
+ echo "Basic perf kallsyms test [Failed: output '$output' does not contain $symbol]"
+ err=1
+ return
+ fi
+
+ if perf kallsyms ErlingHaaland | grep -vq "not found"; then
+ echo "Basic perf kallsyms test [Failed: ErlingHaaland found in the output]"
+ err=1
+ return
+ fi
+ echo "Basic perf kallsyms test [Success]"
+}
+
+test_kallsyms
+exit $err
diff --git a/tools/perf/tests/shell/kvm.sh b/tools/perf/tests/shell/kvm.sh
new file mode 100755
index 000000000..7b2636805
--- /dev/null
+++ b/tools/perf/tests/shell/kvm.sh
@@ -0,0 +1,207 @@
+#!/bin/bash
+# perf kvm tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata=$(mktemp /tmp/__perf_kvm_test.perf.data.XXXXX)
+qemu_pid_file=$(mktemp /tmp/__perf_kvm_test.qemu.pid.XXXXX)
+log_file=$(mktemp /tmp/__perf_kvm_test.live_log.XXXXX)
+
+cleanup() {
+ rm -f "${perfdata}" "${log_file}"
+ if [ -f "${qemu_pid_file}" ]; then
+ if [ -s "${qemu_pid_file}" ]; then
+ qemu_pid=$(cat "${qemu_pid_file}")
+ if [ -n "${qemu_pid}" ]; then
+ kill "${qemu_pid}" 2>/dev/null || true
+ fi
+ fi
+ rm -f "${qemu_pid_file}"
+ fi
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+skip() {
+ echo "Skip: $1"
+ cleanup
+ exit 2
+}
+
+test_kvm_stat() {
+ echo "Testing perf kvm stat"
+
+ local duration
+ local success=false
+ for duration in 1 2 4 8; do
+ echo "Recording kvm events for pid ${qemu_pid} (duration ${duration}s)..."
+ rm -f "${perfdata}" "${perfdata}".old
+ if ! perf kvm stat record -p "${qemu_pid}" -o "${perfdata}" \
+ sleep ${duration} >/dev/null 2>&1; then
+ echo "perf kvm stat record failed, retrying..."
+ continue
+ fi
+
+ if [ -e "${perfdata}" ] && \
+ perf kvm -i "${perfdata}" stat report 2>&1 | grep -q "VM-EXIT"; then
+ success=true
+ break
+ fi
+ echo "No VM-EXIT events found, retrying..."
+ done
+
+ if [ "$success" = false ]; then
+ echo "Failed to find VM-EXIT in report"
+ perf kvm -i "${perfdata}" stat report 2>&1 || true
+ err=1
+ return
+ fi
+
+ echo "perf kvm stat test [Success]"
+}
+
+test_kvm_record_report() {
+ echo "Testing perf kvm record/report"
+
+ local duration
+ local success=false
+ for duration in 1 2 4 8; do
+ echo "Recording kvm profile for pid ${qemu_pid} (duration ${duration}s)..."
+ rm -f "${perfdata}" "${perfdata}".old
+
+ perf kvm --host record -p "${qemu_pid}" -o "${perfdata}" \
+ -e cpu-clock sleep ${duration}
+
+ if [ -e "${perfdata}" ] && \
+ perf kvm -i "${perfdata}" report --stdio 2>&1 | grep -q "Event count"; then
+ success=true
+ break
+ fi
+ echo "No samples or report failed, retrying..."
+ done
+
+ if [ "$success" = false ]; then
+ echo "Failed to report kvm profile"
+ perf kvm -i "${perfdata}" report --stdio 2>&1 || true
+ err=1
+ return
+ fi
+
+ echo "perf kvm record/report test [Success]"
+}
+
+test_kvm_buildid_list() {
+ echo "Testing perf kvm buildid-list"
+
+ # We reuse the perf.data from the previous record test
+ if ! perf kvm --host -i "${perfdata}" buildid-list 2>&1 | grep -q "."; then
+ echo "Failed to list buildids"
+ perf kvm --host -i "${perfdata}" buildid-list 2>&1
+ err=1
+ return
+ fi
+
+ echo "perf kvm buildid-list test [Success]"
+}
+
+test_kvm_stat_live() {
+ echo "Testing perf kvm stat live"
+
+ # Run perf kvm live for 5 seconds, monitoring that PID
+ # Use sleep to keep stdin open but silent, preventing EOF loop or interactive spam
+ if ! sleep 10 | timeout 5s perf kvm stat live -p "${qemu_pid}" > "${log_file}" 2>&1; then
+ retval=$?
+ if [ $retval -ne 124 ] && [ $retval -ne 0 ]; then
+ echo "perf kvm stat live [Failed: perf kvm stat live failed to start or run (ret=$retval)]"
+ head -n 50 "${log_file}"
+ err=1
+ return
+ fi
+ fi
+
+ # Check for some sample data (percentage)
+ if ! grep -E -q "[0-9]+\.[0-9]+%" "${log_file}"; then
+ echo "perf kvm stat live [Failed: no sample percentage found]"
+ head -n 50 "${log_file}"
+ err=1
+ return
+ fi
+
+ echo "perf kvm stat live test [Success]"
+}
+
+test_kvm_default_event() {
+ echo "Testing perf kvm record default event with command line"
+
+ # Check if kvm record with default events handle command line arguments
+ perf kvm record -p "${qemu_pid}" -o /dev/null sleep 1
+
+ echo "perf kvm record default event [Success]"
+}
+
+setup_qemu() {
+ # Find qemu
+ if [ "$(uname -m)" = "x86_64" ]; then
+ qemu="qemu-system-x86_64"
+ elif [ "$(uname -m)" = "aarch64" ]; then
+ qemu="qemu-system-aarch64"
+ elif [ "$(uname -m)" = "s390x" ]; then
+ qemu="qemu-system-s390x"
+ elif [ "$(uname -m)" = "ppc64le" ]; then
+ qemu="qemu-system-ppc64"
+ else
+ qemu="qemu-system-$(uname -m)"
+ fi
+
+ if ! which -s "$qemu"; then
+ skip "$qemu not found"
+ fi
+
+ if [ ! -r /dev/kvm ] || [ ! -w /dev/kvm ]; then
+ skip "/dev/kvm not accessible"
+ fi
+
+ if ! perf kvm stat record -o /dev/null -a sleep 0.01 >/dev/null 2>&1; then
+ skip "No permission to record kvm events"
+ fi
+
+ echo "Starting $qemu..."
+ # Start qemu in background, detached, with pidfile
+ # We use -display none -daemonize and a monitor to keep it alive/controllable if needed
+ # We don't need a real kernel, just KVM active.
+ if ! $qemu -enable-kvm -display none -daemonize -pidfile "${qemu_pid_file}" -monitor none; then
+ echo "Failed to start qemu"
+ err=1
+ return
+ fi
+
+ # Wait a bit for qemu to start
+ sleep 1
+ qemu_pid=$(cat "${qemu_pid_file}")
+
+ if ! kill -0 "${qemu_pid}" 2>/dev/null; then
+ echo "Qemu process failed to stay alive"
+ err=1
+ return
+ fi
+}
+
+setup_qemu
+if [ $err -eq 0 ]; then
+ test_kvm_stat
+ test_kvm_record_report
+ test_kvm_buildid_list
+ test_kvm_stat_live
+ test_kvm_default_event
+fi
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/kwork.sh b/tools/perf/tests/shell/kwork.sh
new file mode 100755
index 000000000..42bfd9382
--- /dev/null
+++ b/tools/perf/tests/shell/kwork.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+# perf kwork tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+# Root permissions required for tracing events.
+if [ "$(id -u)" != 0 ]; then
+ echo "[Skip] No root permission"
+ exit 2
+fi
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test_kwork.perf.data.XXXXX)
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${perfdata}".old
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_kwork_record() {
+ echo "Kwork record"
+ perf kwork record -o "${perfdata}" -- sleep 1
+ echo "Kwork record [Success]"
+}
+
+test_kwork_report() {
+ echo "Kwork report"
+ if ! perf kwork report -i "${perfdata}" | grep -q "Kwork Name"; then
+ echo "Kwork report [Failed missing output]"
+ err=1
+ fi
+ echo "Kwork report [Success]"
+}
+
+test_kwork_latency() {
+ echo "Kwork latency"
+ if ! perf kwork latency -i "${perfdata}" | grep -q "Avg delay"; then
+ echo "Kwork latency [Failed missing output]"
+ err=1
+ fi
+ echo "Kwork latency [Success]"
+}
+
+test_kwork_timehist() {
+ echo "Kwork timehist"
+ if ! perf kwork timehist -i "${perfdata}" | grep -q "Kwork name"; then
+ echo "Kwork timehist [Failed missing output]"
+ err=1
+ fi
+ echo "Kwork timehist [Success]"
+}
+
+test_kwork_top() {
+ echo "Kwork top"
+ if ! perf kwork top -i "${perfdata}" | grep -q "COMMAND"; then
+ echo "Kwork top [Failed missing output]"
+ err=1
+ fi
+ echo "Kwork top [Success]"
+}
+
+test_kwork_record
+test_kwork_report
+test_kwork_latency
+test_kwork_timehist
+test_kwork_top
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/lib/attr.py b/tools/perf/tests/shell/lib/attr.py
new file mode 100644
index 000000000..bfccc727d
--- /dev/null
+++ b/tools/perf/tests/shell/lib/attr.py
@@ -0,0 +1,470 @@
+# SPDX-License-Identifier: GPL-2.0
+
+import configparser
+import os
+import sys
+import glob
+import optparse
+import platform
+import tempfile
+import logging
+import re
+import shutil
+import subprocess
+
+def data_equal(a, b):
+ # Allow multiple values in assignment separated by '|'
+ a_list = a.split('|')
+ b_list = b.split('|')
+
+ for a_item in a_list:
+ for b_item in b_list:
+ if (a_item == b_item):
+ return True
+ elif (a_item == '*') or (b_item == '*'):
+ return True
+
+ return False
+
+class Fail(Exception):
+ def __init__(self, test, msg):
+ self.msg = msg
+ self.test = test
+ def getMsg(self):
+ return '\'%s\' - %s' % (self.test.path, self.msg)
+
+class Notest(Exception):
+ def __init__(self, test, arch):
+ self.arch = arch
+ self.test = test
+ def getMsg(self):
+ return '[%s] \'%s\'' % (self.arch, self.test.path)
+
+class Unsup(Exception):
+ def __init__(self, test):
+ self.test = test
+ def getMsg(self):
+ return '\'%s\'' % self.test.path
+
+class Event(dict):
+ terms = [
+ 'cpu',
+ 'flags',
+ 'type',
+ 'size',
+ 'config',
+ 'sample_period',
+ 'sample_type',
+ 'read_format',
+ 'disabled',
+ 'inherit',
+ 'pinned',
+ 'exclusive',
+ 'exclude_user',
+ 'exclude_kernel',
+ 'exclude_hv',
+ 'exclude_idle',
+ 'mmap',
+ 'comm',
+ 'freq',
+ 'inherit_stat',
+ 'enable_on_exec',
+ 'task',
+ 'watermark',
+ 'precise_ip',
+ 'mmap_data',
+ 'sample_id_all',
+ 'exclude_host',
+ 'exclude_guest',
+ 'exclude_callchain_kernel',
+ 'exclude_callchain_user',
+ 'wakeup_events',
+ 'bp_type',
+ 'config1',
+ 'config2',
+ 'branch_sample_type',
+ 'sample_regs_user',
+ 'sample_stack_user',
+ ]
+
+ def add(self, data):
+ for key, val in data:
+ log.debug(" %s = %s" % (key, val))
+ self[key] = val
+
+ def __init__(self, name, data, base):
+ log.debug(" Event %s" % name);
+ self.name = name;
+ self.group = ''
+ self.add(base)
+ self.add(data)
+
+ 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]):
+ return False
+ return True
+
+ def optional(self):
+ if 'optional' in self and self['optional'] == '1':
+ return True
+ return False
+
+ def diff(self, other):
+ for t in Event.terms:
+ if t not in self or t not in other:
+ continue
+ if not data_equal(self[t], other[t]):
+ log.warning("expected %s=%s, got %s" % (t, self[t], other[t]))
+
+def parse_version(version):
+ if not version:
+ return None
+ return [int(v) for v in version.split(".")[0:2]]
+
+# Test file description needs to have following sections:
+# [config]
+# - just single instance in file
+# - needs to specify:
+# 'command' - perf command name
+# 'args' - special command arguments
+# 'ret' - Skip test if Perf doesn't exit with this value (0 by default)
+# 'test_ret'- If set to 'true', fail test instead of skipping for 'ret' argument
+# 'arch' - architecture specific test (optional)
+# comma separated list, ! at the beginning
+# negates it.
+# 'auxv' - Truthy statement that is evaled in the scope of the auxv map. When false,
+# the test is skipped. For example 'auxv["AT_HWCAP"] == 10'. (optional)
+# 'kernel_since' - Inclusive kernel version from which the test will start running. Only the
+# first two values are supported, for example "6.1" (optional)
+# 'kernel_until' - Exclusive kernel version from which the test will stop running. (optional)
+# [eventX:base]
+# - one or multiple instances in file
+# - expected values assignments
+class Test(object):
+ def __init__(self, path, options):
+ parser = configparser.ConfigParser()
+ parser.read(path)
+
+ log.warning("running '%s'" % path)
+
+ self.path = path
+ self.test_dir = options.test_dir
+ self.perf = options.perf
+ self.command = parser.get('config', 'command')
+ self.args = parser.get('config', 'args')
+
+ try:
+ self.ret = parser.get('config', 'ret')
+ except:
+ self.ret = 0
+
+ self.test_ret = parser.getboolean('config', 'test_ret', fallback=False)
+
+ try:
+ self.arch = parser.get('config', 'arch')
+ log.warning("test limitation '%s'" % self.arch)
+ except:
+ self.arch = ''
+
+ self.auxv = parser.get('config', 'auxv', fallback=None)
+ self.kernel_since = parse_version(parser.get('config', 'kernel_since', fallback=None))
+ self.kernel_until = parse_version(parser.get('config', 'kernel_until', fallback=None))
+ self.expect = {}
+ self.result = {}
+ log.debug(" loading expected events");
+ self.load_events(path, self.expect)
+
+ def is_event(self, name):
+ if name.find("event") == -1:
+ return False
+ else:
+ return True
+
+ def skip_test_kernel_since(self):
+ if not self.kernel_since:
+ return False
+ return not self.kernel_since <= parse_version(platform.release())
+
+ def skip_test_kernel_until(self):
+ if not self.kernel_until:
+ return False
+ return not parse_version(platform.release()) < self.kernel_until
+
+ def skip_test_auxv(self):
+ def new_auxv(a, pattern):
+ items = list(filter(None, pattern.split(a)))
+ # AT_HWCAP is hex but doesn't have a prefix, so special case it
+ if items[0] == "AT_HWCAP":
+ value = int(items[-1], 16)
+ else:
+ try:
+ value = int(items[-1], 0)
+ except:
+ value = items[-1]
+ return (items[0], value)
+
+ if not self.auxv:
+ return False
+ 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)
+
+ 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
+
+ # Allow multiple values in assignment separated by ','
+ arch_list = self.arch.split(',')
+
+ # Handle negated list such as !s390x,ppc
+ if arch_list[0][0] == '!':
+ 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
+
+ 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
+
+ def restore_sample_rate(self, value=10000):
+ try:
+ # Check value of sample_rate
+ with open("/proc/sys/kernel/perf_event_max_sample_rate", "r") as fIn:
+ curr_value = fIn.readline()
+ # If too low restore to reasonable value
+ if not curr_value or int(curr_value) < int(value):
+ with open("/proc/sys/kernel/perf_event_max_sample_rate", "w") as fOut:
+ fOut.write(str(value))
+
+ except IOError as e:
+ log.warning("couldn't restore sample_rate value: I/O error %s" % e)
+ except ValueError as e:
+ log.warning("couldn't restore sample_rate value: Value error %s" % e)
+ except TypeError as e:
+ log.warning("couldn't restore sample_rate value: Type error %s" % e)
+
+ def load_events(self, path, events):
+ parser_event = configparser.ConfigParser()
+ parser_event.read(path)
+
+ # The event record section header contains 'event' word,
+ # optionaly followed by ':' allowing to load 'parent
+ # event' first as a base
+ for section in filter(self.is_event, parser_event.sections()):
+
+ parser_items = parser_event.items(section);
+ base_items = {}
+
+ # Read parent event if there's any
+ if (':' in section):
+ base = section[section.index(':') + 1:]
+ parser_base = configparser.ConfigParser()
+ parser_base.read(self.test_dir + '/' + base)
+ base_items = parser_base.items('event')
+
+ e = Event(section, parser_items, base_items)
+ events[section] = e
+
+ 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")
+
+ self.restore_sample_rate()
+ cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir,
+ self.perf, self.command, tempdir, self.args)
+ ret = os.WEXITSTATUS(os.system(cmd))
+
+ log.info(" '%s' ret '%s', expected '%s'" % (cmd, str(ret), str(self.ret)))
+
+ if not data_equal(str(ret), str(self.ret)):
+ if self.test_ret:
+ raise Fail(self, "Perf exit code failure")
+ else:
+ raise Unsup(self)
+
+ def compare(self, expect, result):
+ match = {}
+
+ log.debug(" compare");
+
+ # For each expected event find all matching
+ # events in result. Fail if there's not any.
+ for exp_name, exp_event in expect.items():
+ exp_list = []
+ res_event = {}
+ log.debug(" matching [%s]" % exp_name)
+ for res_name, res_event in result.items():
+ log.debug(" to [%s]" % res_name)
+ if (exp_event.equal(res_event)):
+ exp_list.append(res_name)
+ log.debug(" ->OK")
+ else:
+ log.debug(" ->FAIL");
+
+ log.debug(" match: [%s] matches %s" % (exp_name, str(exp_list)))
+
+ # we did not any matching event - fail
+ if not exp_list:
+ if exp_event.optional():
+ log.debug(" %s does not match, but is optional" % exp_name)
+ else:
+ if not res_event:
+ log.debug(" res_event is empty");
+ else:
+ exp_event.diff(res_event)
+ raise Fail(self, 'match failure');
+
+ match[exp_name] = exp_list
+
+ # For each defined group in the expected events
+ # check we match the same group in the result.
+ for exp_name, exp_event in expect.items():
+ group = exp_event.group
+
+ if (group == ''):
+ continue
+
+ for res_name in match[exp_name]:
+ res_group = result[res_name].group
+ if res_group not in match[group]:
+ raise Fail(self, 'group failure')
+
+ log.debug(" group: [%s] matches group leader %s" %
+ (exp_name, str(match[group])))
+
+ log.debug(" matched")
+
+ def resolve_groups(self, events):
+ for name, event in events.items():
+ group_fd = event['group_fd'];
+ if group_fd == '-1':
+ continue;
+
+ for iname, ievent in events.items():
+ if (ievent['fd'] == group_fd):
+ event.group = iname
+ log.debug('[%s] has group leader [%s]' % (name, iname))
+ break;
+
+ def run(self):
+ tempdir = tempfile.mkdtemp();
+
+ try:
+ # run the test script
+ self.run_cmd(tempdir);
+
+ # load events expectation for the test
+ log.debug(" loading result events");
+ for f in glob.glob(tempdir + '/event*'):
+ self.load_events(f, self.result);
+
+ # resolve group_fd to event names
+ self.resolve_groups(self.expect);
+ self.resolve_groups(self.result);
+
+ # do the expectation - results matching - both ways
+ self.compare(self.expect, self.result)
+ self.compare(self.result, self.expect)
+
+ finally:
+ # cleanup
+ shutil.rmtree(tempdir)
+
+
+def run_tests(options):
+ for f in glob.glob(options.test_dir + '/' + options.test):
+ try:
+ Test(f, options).run()
+ except Unsup as obj:
+ log.warning("unsupp %s" % obj.getMsg())
+ except Notest as obj:
+ log.warning("skipped %s" % obj.getMsg())
+
+def setup_log(verbose):
+ global log
+ level = logging.CRITICAL
+
+ if verbose == 1:
+ level = logging.WARNING
+ if verbose == 2:
+ level = logging.INFO
+ if verbose >= 3:
+ level = logging.DEBUG
+
+ log = logging.getLogger('test')
+ log.setLevel(level)
+ ch = logging.StreamHandler()
+ ch.setLevel(level)
+ formatter = logging.Formatter('%(message)s')
+ ch.setFormatter(formatter)
+ log.addHandler(ch)
+
+USAGE = '''%s [OPTIONS]
+ -d dir # tests dir
+ -p path # perf binary
+ -t test # single test
+ -v # verbose level
+''' % sys.argv[0]
+
+def main():
+ parser = optparse.OptionParser(usage=USAGE)
+
+ parser.add_option("-t", "--test",
+ action="store", type="string", dest="test")
+ parser.add_option("-d", "--test-dir",
+ action="store", type="string", dest="test_dir")
+ parser.add_option("-p", "--perf",
+ action="store", type="string", dest="perf")
+ parser.add_option("-v", "--verbose",
+ default=0, action="count", dest="verbose")
+
+ options, args = parser.parse_args()
+ if args:
+ parser.error('FAILED wrong arguments %s' % ' '.join(args))
+ return -1
+
+ setup_log(options.verbose)
+
+ if not options.test_dir:
+ print('FAILED no -d option specified')
+ sys.exit(-1)
+
+ if not options.test:
+ options.test = 'test*'
+
+ try:
+ run_tests(options)
+
+ except Fail as obj:
+ print("FAILED %s" % obj.getMsg())
+ sys.exit(-1)
+
+ sys.exit(0)
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/perf/tests/shell/lib/perf_has_symbol.sh b/tools/perf/tests/shell/lib/perf_has_symbol.sh
new file mode 100644
index 000000000..0b35cce0b
--- /dev/null
+++ b/tools/perf/tests/shell/lib/perf_has_symbol.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+perf_has_symbol()
+{
+ if perf test -vv -F "Symbols" 2>&1 | grep "[[:space:]]$1$"; then
+ echo "perf does have symbol '$1'"
+ return 0
+ fi
+ echo "perf does not have symbol '$1'"
+ return 1
+}
+
+skip_test_missing_symbol()
+{
+ if ! perf_has_symbol "$1" ; then
+ echo "perf is missing symbols - skipping test"
+ exit 2
+ fi
+ return 0
+}
diff --git a/tools/perf/tests/shell/lib/perf_json_output_lint.py b/tools/perf/tests/shell/lib/perf_json_output_lint.py
new file mode 100644
index 000000000..dafbde56c
--- /dev/null
+++ b/tools/perf/tests/shell/lib/perf_json_output_lint.py
@@ -0,0 +1,114 @@
+#!/usr/bin/python
+# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
+# Basic sanity check of perf JSON output as specified in the man page.
+
+import argparse
+import sys
+import json
+
+ap = argparse.ArgumentParser()
+ap.add_argument('--no-args', action='store_true')
+ap.add_argument('--interval', action='store_true')
+ap.add_argument('--system-wide-no-aggr', action='store_true')
+ap.add_argument('--system-wide', action='store_true')
+ap.add_argument('--event', action='store_true')
+ap.add_argument('--per-core', action='store_true')
+ap.add_argument('--per-thread', action='store_true')
+ap.add_argument('--per-cache', action='store_true')
+ap.add_argument('--per-cluster', action='store_true')
+ap.add_argument('--per-die', action='store_true')
+ap.add_argument('--per-node', action='store_true')
+ap.add_argument('--per-socket', action='store_true')
+ap.add_argument('--metric-only', action='store_true')
+ap.add_argument('--file', type=argparse.FileType('r'), default=sys.stdin)
+args = ap.parse_args()
+
+Lines = args.file.readlines()
+
+def isfloat(num):
+ try:
+ float(num)
+ return True
+ except ValueError:
+ return False
+
+
+def isint(num):
+ try:
+ int(num)
+ return True
+ except ValueError:
+ return False
+
+def is_counter_value(num):
+ return isfloat(num) or num == '<not counted>' or num == '<not supported>'
+
+def is_metric_value(num):
+ return isfloat(num) or num == 'none'
+
+def check_json_output(expected_items):
+ checks = {
+ 'counters': lambda x: isfloat(x),
+ 'core': lambda x: True,
+ 'counter-value': lambda x: is_counter_value(x),
+ 'cgroup': lambda x: True,
+ 'cpu': lambda x: isint(x),
+ 'cache': lambda x: True,
+ 'cluster': lambda x: True,
+ 'die': lambda x: True,
+ 'event': lambda x: True,
+ 'event-runtime': lambda x: isfloat(x),
+ 'interval': lambda x: isfloat(x),
+ 'metric-unit': lambda x: True,
+ 'metric-value': lambda x: is_metric_value(x),
+ 'metric-threshold': lambda x: x in ['unknown', 'good', 'less good', 'nearly bad', 'bad'],
+ 'metricgroup': lambda x: True,
+ 'node': lambda x: True,
+ 'pcnt-running': lambda x: isfloat(x),
+ 'socket': lambda x: True,
+ 'thread': lambda x: True,
+ 'unit': lambda x: True,
+ }
+ input = '[\n' + ','.join(Lines) + '\n]'
+ for item in json.loads(input):
+ if expected_items != -1:
+ count = len(item)
+ if count not in expected_items and count >= 1 and count <= 7 and 'metric-value' in item:
+ # Events that generate >1 metric may have isolated metric
+ # values and possibly other prefixes like interval, core,
+ # counters, or event-runtime/pcnt-running from multiplexing.
+ pass
+ elif count not in expected_items and count >= 1 and count <= 5 and 'metricgroup' in item:
+ pass
+ elif count - 1 in expected_items and 'metric-threshold' in item:
+ pass
+ elif count in expected_items and 'insn per cycle' in item:
+ pass
+ elif count not in expected_items:
+ raise RuntimeError(f'wrong number of fields. counted {count} expected {expected_items}'
+ f' in \'{item}\'')
+ for key, value in item.items():
+ if key not in checks:
+ if args.metric_only:
+ continue
+ raise RuntimeError(f'Unexpected key: key={key} value={value}')
+ if not checks[key](value):
+ raise RuntimeError(f'Check failed for: key={key} value={value}')
+
+
+try:
+ if args.no_args or args.system_wide or args.event:
+ expected_items = [5, 7]
+ elif args.interval or args.per_thread or args.system_wide_no_aggr:
+ expected_items = [6, 8]
+ elif args.per_core or args.per_socket or args.per_node or args.per_die or args.per_cluster or args.per_cache:
+ expected_items = [7, 9]
+ elif args.metric_only:
+ expected_items = [1, 2]
+ else:
+ # If no option is specified, don't check the number of items.
+ expected_items = -1
+ check_json_output(expected_items)
+except:
+ print('Test failed for input:\n' + '\n'.join(Lines))
+ raise
diff --git a/tools/perf/tests/shell/lib/perf_metric_validation.py b/tools/perf/tests/shell/lib/perf_metric_validation.py
new file mode 100644
index 000000000..3d52f94f2
--- /dev/null
+++ b/tools/perf/tests/shell/lib/perf_metric_validation.py
@@ -0,0 +1,606 @@
+# SPDX-License-Identifier: GPL-2.0
+import re
+import csv
+import json
+import argparse
+from pathlib import Path
+import subprocess
+
+
+class TestError:
+ def __init__(self, metric: list[str], wl: str, value: list[float], low: float, up=float('nan'), description=str()):
+ self.metric: list = metric # multiple metrics in relationship type tests
+ self.workloads = [wl] # multiple workloads possible
+ self.collectedValue: list = value
+ self.valueLowBound = low
+ self.valueUpBound = up
+ self.description = description
+
+ def __repr__(self) -> str:
+ if len(self.metric) > 1:
+ return "\nMetric Relationship Error: \tThe collected value of metric {0}\n\
+ \tis {1} in workload(s): {2} \n\
+ \tbut expected value range is [{3}, {4}]\n\
+ \tRelationship rule description: \'{5}\'".format(self.metric, self.collectedValue, self.workloads,
+ self.valueLowBound, self.valueUpBound, self.description)
+ elif len(self.collectedValue) == 0:
+ return "\nNo Metric Value Error: \tMetric {0} returns with no value \n\
+ \tworkload(s): {1}".format(self.metric, self.workloads)
+ else:
+ return "\nWrong Metric Value Error: \tThe collected value of metric {0}\n\
+ \tis {1} in workload(s): {2}\n\
+ \tbut expected value range is [{3}, {4}]"\
+ .format(self.metric, self.collectedValue, self.workloads,
+ self.valueLowBound, self.valueUpBound)
+
+
+class Validator:
+ def __init__(self, rulefname, reportfname='', t=5, debug=False, datafname='', fullrulefname='',
+ workload='true', metrics='', cputype='cpu'):
+ self.rulefname = rulefname
+ self.reportfname = reportfname
+ self.rules = None
+ self.collectlist: str = metrics
+ self.metrics = self.__set_metrics(metrics)
+ self.skiplist = set()
+ self.tolerance = t
+ self.cputype = cputype
+
+ self.workloads = [x for x in workload.split(",") if x]
+ self.wlidx = 0 # idx of current workloads
+ self.allresults = dict() # metric results of all workload
+ self.alltotalcnt = dict()
+ self.allpassedcnt = dict()
+
+ self.results = dict() # metric results of current workload
+ # vars for test pass/failure statistics
+ # metrics with no results or negative results, neg result counts failed tests
+ self.ignoremetrics = set()
+ self.totalcnt = 0
+ self.passedcnt = 0
+ # vars for errors
+ self.errlist = list()
+
+ # vars for Rule Generator
+ self.pctgmetrics = set() # Percentage rule
+
+ # vars for debug
+ self.datafname = datafname
+ self.debug = debug
+ self.fullrulefname = fullrulefname
+
+ def __set_metrics(self, metrics=''):
+ if metrics != '':
+ return set(metrics.split(","))
+ else:
+ return set()
+
+ def read_json(self, filename: str) -> dict:
+ try:
+ with open(Path(filename).resolve(), "r") as f:
+ data = json.loads(f.read())
+ except OSError as e:
+ print(f"Error when reading file {e}")
+ sys.exit()
+
+ return data
+
+ def json_dump(self, data, output_file):
+ parent = Path(output_file).parent
+ if not parent.exists():
+ parent.mkdir(parents=True)
+
+ with open(output_file, "w+") as output_file:
+ json.dump(data,
+ output_file,
+ ensure_ascii=True,
+ indent=4)
+
+ def get_results(self, idx: int = 0):
+ return self.results.get(idx)
+
+ def get_bounds(self, lb, ub, error, alias={}, ridx: int = 0) -> list:
+ """
+ Get bounds and tolerance from lb, ub, and error.
+ If missing lb, use 0.0; missing ub, use float('inf); missing error, use self.tolerance.
+
+ @param lb: str/float, lower bound
+ @param ub: str/float, upper bound
+ @param error: float/str, error tolerance
+ @returns: lower bound, return inf if the lower bound is a metric value and is not collected
+ upper bound, return -1 if the upper bound is a metric value and is not collected
+ tolerance, denormalized base on upper bound value
+ """
+ # init ubv and lbv to invalid values
+ def get_bound_value(bound, initval, ridx):
+ val = initval
+ if isinstance(bound, int) or isinstance(bound, float):
+ val = bound
+ elif isinstance(bound, str):
+ if bound == '':
+ val = float("inf")
+ elif bound in alias:
+ vall = self.get_value(alias[ub], ridx)
+ if vall:
+ val = vall[0]
+ elif bound.replace('.', '1').isdigit():
+ val = float(bound)
+ else:
+ print("Wrong bound: {0}".format(bound))
+ else:
+ print("Wrong bound: {0}".format(bound))
+ return val
+
+ ubv = get_bound_value(ub, -1, ridx)
+ lbv = get_bound_value(lb, float('inf'), ridx)
+ t = get_bound_value(error, self.tolerance, ridx)
+
+ # denormalize error threshold
+ denormerr = t * ubv / 100 if ubv != 100 and ubv > 0 else t
+
+ return lbv, ubv, denormerr
+
+ def get_value(self, name: str, ridx: int = 0) -> list:
+ """
+ Get value of the metric from self.results.
+ If result of this metric is not provided, the metric name will be added into self.ignoremetics.
+ All future test(s) on this metric will fail.
+
+ @param name: name of the metric
+ @returns: list with value found in self.results; list is empty when value is not found.
+ """
+ results = []
+ data = self.results[ridx] if ridx in self.results else self.results[0]
+ if name not in self.ignoremetrics:
+ if name in data:
+ results.append(data[name])
+ elif name.replace('.', '1').isdigit():
+ results.append(float(name))
+ else:
+ self.ignoremetrics.add(name)
+ return results
+
+ def check_bound(self, val, lb, ub, err):
+ return True if val <= ub + err and val >= lb - err else False
+
+ # Positive Value Sanity check
+ def pos_val_test(self):
+ """
+ Check if metrics value are non-negative.
+ One metric is counted as one test.
+ Failure: when metric value is negative or not provided.
+ Metrics with negative value will be added into self.ignoremetrics.
+ """
+ negmetric = dict()
+ pcnt = 0
+ tcnt = 0
+ rerun = list()
+ results = self.get_results()
+ if not results:
+ return
+ for name, val in results.items():
+ if val < 0:
+ negmetric[name] = val
+ rerun.append(name)
+ else:
+ pcnt += 1
+ tcnt += 1
+ # The first round collect_perf() run these metrics with simple workload
+ # "true". We give metrics a second chance with a longer workload if less
+ # than 20 metrics failed positive test.
+ if len(rerun) > 0 and len(rerun) < 20:
+ second_results = dict()
+ self.second_test(rerun, second_results)
+ for name, val in second_results.items():
+ if name not in negmetric:
+ continue
+ if val >= 0:
+ del negmetric[name]
+ pcnt += 1
+
+ if len(negmetric.keys()):
+ self.ignoremetrics.update(negmetric.keys())
+ self.errlist.extend(
+ [TestError([m], self.workloads[self.wlidx], negmetric[m], 0) for m in negmetric.keys()])
+
+ return
+
+ def evaluate_formula(self, formula: str, alias: dict, ridx: int = 0):
+ """
+ Evaluate the value of formula.
+
+ @param formula: the formula to be evaluated
+ @param alias: the dict has alias to metric name mapping
+ @returns: value of the formula is success; -1 if the one or more metric value not provided
+ """
+ stack = []
+ b = 0
+ errs = []
+ sign = "+"
+ f = str()
+
+ # TODO: support parenthesis?
+ for i in range(len(formula)):
+ if i+1 == len(formula) or formula[i] in ('+', '-', '*', '/'):
+ s = alias[formula[b:i]] if i + \
+ 1 < len(formula) else alias[formula[b:]]
+ v = self.get_value(s, ridx)
+ if not v:
+ errs.append(s)
+ else:
+ f = f + "{0}(={1:.4f})".format(s, v[0])
+ if sign == "*":
+ stack[-1] = stack[-1] * v
+ elif sign == "/":
+ stack[-1] = stack[-1] / v
+ elif sign == '-':
+ stack.append(-v[0])
+ else:
+ stack.append(v[0])
+ if i + 1 < len(formula):
+ sign = formula[i]
+ f += sign
+ b = i + 1
+
+ if len(errs) > 0:
+ return -1, "Metric value missing: "+','.join(errs)
+
+ val = sum(stack)
+ return val, f
+
+ # Relationships Tests
+ def relationship_test(self, rule: dict):
+ """
+ Validate if the metrics follow the required relationship in the rule.
+ eg. lower_bound <= eval(formula)<= upper_bound
+ One rule is counted as ont test.
+ Failure: when one or more metric result(s) not provided, or when formula evaluated outside of upper/lower bounds.
+
+ @param rule: dict with metric name(+alias), formula, and required upper and lower bounds.
+ """
+ alias = dict()
+ for m in rule['Metrics']:
+ alias[m['Alias']] = m['Name']
+ lbv, ubv, t = self.get_bounds(
+ rule['RangeLower'], rule['RangeUpper'], rule['ErrorThreshold'], alias, ridx=rule['RuleIndex'])
+ val, f = self.evaluate_formula(
+ rule['Formula'], alias, ridx=rule['RuleIndex'])
+
+ lb = rule['RangeLower']
+ ub = rule['RangeUpper']
+ if isinstance(lb, str):
+ if lb in alias:
+ lb = alias[lb]
+ if isinstance(ub, str):
+ if ub in alias:
+ ub = alias[ub]
+
+ if val == -1:
+ self.errlist.append(TestError([m['Name'] for m in rule['Metrics']], self.workloads[self.wlidx], [],
+ lb, ub, rule['Description']))
+ elif not self.check_bound(val, lbv, ubv, t):
+ self.errlist.append(TestError([m['Name'] for m in rule['Metrics']], self.workloads[self.wlidx], [val],
+ lb, ub, rule['Description']))
+ else:
+ self.passedcnt += 1
+ self.totalcnt += 1
+
+ return
+
+ # Single Metric Test
+ def single_test(self, rule: dict):
+ """
+ Validate if the metrics are in the required value range.
+ eg. lower_bound <= metrics_value <= upper_bound
+ One metric is counted as one test in this type of test.
+ One rule may include one or more metrics.
+ Failure: when the metric value not provided or the value is outside the bounds.
+ This test updates self.total_cnt.
+
+ @param rule: dict with metrics to validate and the value range requirement
+ """
+ lbv, ubv, t = self.get_bounds(
+ rule['RangeLower'], rule['RangeUpper'], rule['ErrorThreshold'])
+ metrics = rule['Metrics']
+ passcnt = 0
+ totalcnt = 0
+ failures = dict()
+ rerun = list()
+ for m in metrics:
+ totalcnt += 1
+ result = self.get_value(m['Name'])
+ if len(result) > 0 and self.check_bound(result[0], lbv, ubv, t) or m['Name'] in self.skiplist:
+ passcnt += 1
+ else:
+ failures[m['Name']] = result
+ rerun.append(m['Name'])
+
+ if len(rerun) > 0 and len(rerun) < 20:
+ second_results = dict()
+ self.second_test(rerun, second_results)
+ for name, val in second_results.items():
+ if name not in failures:
+ continue
+ if self.check_bound(val, lbv, ubv, t):
+ passcnt += 1
+ del failures[name]
+ else:
+ failures[name] = [val]
+ self.results[0][name] = val
+
+ self.totalcnt += totalcnt
+ self.passedcnt += passcnt
+ if len(failures.keys()) != 0:
+ self.errlist.extend([TestError([name], self.workloads[self.wlidx], val,
+ rule['RangeLower'], rule['RangeUpper']) for name, val in failures.items()])
+
+ return
+
+ def create_report(self):
+ """
+ Create final report and write into a JSON file.
+ """
+ print(self.errlist)
+
+ if self.debug:
+ allres = [{"Workload": self.workloads[i], "Results": self.allresults[i]}
+ for i in range(0, len(self.workloads))]
+ self.json_dump(allres, self.datafname)
+
+ def check_rule(self, testtype, metric_list):
+ """
+ Check if the rule uses metric(s) that not exist in current platform.
+
+ @param metric_list: list of metrics from the rule.
+ @return: False when find one metric out in Metric file. (This rule should not skipped.)
+ True when all metrics used in the rule are found in Metric file.
+ """
+ if testtype == "RelationshipTest":
+ for m in metric_list:
+ if m['Name'] not in self.metrics:
+ return False
+ return True
+
+ # Start of Collector and Converter
+ def convert(self, data: list, metricvalues: dict):
+ """
+ Convert collected metric data from the -j output to dict of {metric_name:value}.
+ """
+ for json_string in data:
+ try:
+ result = json.loads(json_string)
+ if "metric-unit" in result and result["metric-unit"] != "(null)" and result["metric-unit"] != "":
+ name = result["metric-unit"].split(" ")[1] if len(result["metric-unit"].split(" ")) > 1 \
+ else result["metric-unit"]
+ metricvalues[name.lower()] = float(result["metric-value"])
+ except ValueError as error:
+ continue
+ return
+
+ def _run_perf(self, metric, workload: str):
+ tool = 'perf'
+ command = [tool, 'stat', '--cputype', self.cputype, '-j', '-M', f"{metric}", "-a"]
+ wl = workload.split()
+ command.extend(wl)
+ print(" ".join(command))
+ cmd = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8')
+ lines = cmd.stderr.splitlines() + cmd.stdout.splitlines()
+ data = []
+ for line in lines:
+ line = line.strip()
+ if line.startswith('{') and line.endswith('}'):
+ data.append(line)
+ return data
+
+ def collect_perf(self, workload: str):
+ """
+ Collect metric data with "perf stat -M" on given workload with -a and -j.
+ """
+ self.results = dict()
+ print(f"Starting perf collection")
+ print(f"Long workload: {workload}")
+ collectlist = dict()
+ if self.collectlist != "":
+ collectlist[0] = {x for x in self.collectlist.split(",")}
+ else:
+ collectlist[0] = set(list(self.metrics))
+ # Create metric set for relationship rules
+ for rule in self.rules:
+ if rule["TestType"] == "RelationshipTest":
+ metrics = [m["Name"] for m in rule["Metrics"]]
+ if not any(m not in collectlist[0] for m in metrics):
+ collectlist[rule["RuleIndex"]] = [
+ ",".join(list(set(metrics)))]
+
+ for idx, metrics in collectlist.items():
+ if idx == 0:
+ wl = "true"
+ else:
+ wl = workload
+ for metric in metrics:
+ data = self._run_perf(metric, wl)
+ if idx not in self.results:
+ self.results[idx] = dict()
+ self.convert(data, self.results[idx])
+ return
+
+ def second_test(self, collectlist, second_results):
+ workload = self.workloads[self.wlidx]
+ for metric in collectlist:
+ data = self._run_perf(metric, workload)
+ self.convert(data, second_results)
+
+ # End of Collector and Converter
+
+ # Start of Rule Generator
+ def parse_perf_metrics(self):
+ """
+ Read and parse perf metric file:
+ 1) find metrics with '1%' or '100%' as ScaleUnit for Percent check
+ 2) create metric name list
+ """
+ command = ['perf', 'list', '-j', '--details', 'metrics']
+ cmd = subprocess.run(command, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, encoding='utf-8')
+ try:
+ data = json.loads(cmd.stdout)
+ for m in data:
+ if 'MetricName' not in m:
+ print("Warning: no metric name")
+ continue
+ if 'Unit' in m and m['Unit'] != self.cputype:
+ continue
+ name = m['MetricName'].lower()
+ self.metrics.add(name)
+ if 'ScaleUnit' in m and (m['ScaleUnit'] == '1%' or m['ScaleUnit'] == '100%'):
+ self.pctgmetrics.add(name.lower())
+ except ValueError as error:
+ print(f"Error when parsing metric data")
+ sys.exit()
+
+ return
+
+ def remove_unsupported_rules(self, rules):
+ new_rules = []
+ for rule in rules:
+ add_rule = True
+ for m in rule["Metrics"]:
+ if m["Name"] in self.skiplist or m["Name"] not in self.metrics:
+ add_rule = False
+ break
+ if add_rule:
+ new_rules.append(rule)
+ return new_rules
+
+ def create_rules(self):
+ """
+ Create full rules which includes:
+ 1) All the rules from the "relationshi_rules" file
+ 2) SingleMetric rule for all the 'percent' metrics
+
+ Reindex all the rules to avoid repeated RuleIndex
+ """
+ data = self.read_json(self.rulefname)
+ rules = data['RelationshipRules']
+ self.skiplist = set([name.lower() for name in data['SkipList']])
+ self.rules = self.remove_unsupported_rules(rules)
+ pctgrule = {'RuleIndex': 0,
+ 'TestType': 'SingleMetricTest',
+ 'RangeLower': '0',
+ 'RangeUpper': '100',
+ 'ErrorThreshold': self.tolerance,
+ 'Description': 'Metrics in percent unit have value with in [0, 100]',
+ 'Metrics': [{'Name': m.lower()} for m in self.pctgmetrics]}
+ self.rules.append(pctgrule)
+
+ # Re-index all rules to avoid repeated RuleIndex
+ idx = 1
+ for r in self.rules:
+ r['RuleIndex'] = idx
+ idx += 1
+
+ if self.debug:
+ # TODO: need to test and generate file name correctly
+ data = {'RelationshipRules': self.rules, 'SupportedMetrics': [
+ {"MetricName": name} for name in self.metrics]}
+ self.json_dump(data, self.fullrulefname)
+
+ return
+ # End of Rule Generator
+
+ def _storewldata(self, key):
+ '''
+ Store all the data of one workload into the corresponding data structure for all workloads.
+ @param key: key to the dictionaries (index of self.workloads).
+ '''
+ self.allresults[key] = self.results
+ self.alltotalcnt[key] = self.totalcnt
+ self.allpassedcnt[key] = self.passedcnt
+
+ # Initialize data structures before data validation of each workload
+ def _init_data(self):
+
+ testtypes = ['PositiveValueTest',
+ 'RelationshipTest', 'SingleMetricTest']
+ self.results = dict()
+ self.ignoremetrics = set()
+ self.errlist = list()
+ self.totalcnt = 0
+ self.passedcnt = 0
+
+ def test(self):
+ '''
+ The real entry point of the test framework.
+ This function loads the validation rule JSON file and Standard Metric file to create rules for
+ testing and namemap dictionaries.
+ It also reads in result JSON file for testing.
+
+ In the test process, it passes through each rule and launch correct test function bases on the
+ 'TestType' field of the rule.
+
+ The final report is written into a JSON file.
+ '''
+ if not self.collectlist:
+ self.parse_perf_metrics()
+ if not self.metrics:
+ print("No metric found for testing")
+ return 0
+ self.create_rules()
+ for i in range(0, len(self.workloads)):
+ self.wlidx = i
+ self._init_data()
+ self.collect_perf(self.workloads[i])
+ # Run positive value test
+ self.pos_val_test()
+ for r in self.rules:
+ # skip rules that uses metrics not exist in this platform
+ testtype = r['TestType']
+ if not self.check_rule(testtype, r['Metrics']):
+ continue
+ if testtype == 'RelationshipTest':
+ self.relationship_test(r)
+ elif testtype == 'SingleMetricTest':
+ self.single_test(r)
+ else:
+ print("Unsupported Test Type: ", testtype)
+ print("Workload: ", self.workloads[i])
+ print("Total Test Count: ", self.totalcnt)
+ print("Passed Test Count: ", self.passedcnt)
+ self._storewldata(i)
+ self.create_report()
+ return len(self.errlist) > 0
+# End of Class Validator
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(
+ description="Launch metric value validation")
+
+ parser.add_argument(
+ "-rule", help="Base validation rule file", required=True)
+ parser.add_argument(
+ "-output_dir", help="Path for validator output file, report file", required=True)
+ parser.add_argument("-debug", help="Debug run, save intermediate data to files",
+ action="store_true", default=False)
+ parser.add_argument(
+ "-wl", help="Workload to run while data collection", default="true")
+ parser.add_argument("-m", help="Metric list to validate", default="")
+ parser.add_argument("-cputype", help="Only test metrics for the given CPU/PMU type",
+ default="cpu")
+ args = parser.parse_args()
+ outpath = Path(args.output_dir)
+ reportf = Path.joinpath(outpath, 'perf_report.json')
+ fullrule = Path.joinpath(outpath, 'full_rule.json')
+ datafile = Path.joinpath(outpath, 'perf_data.json')
+
+ validator = Validator(args.rule, reportf, debug=args.debug,
+ datafname=datafile, fullrulefname=fullrule, workload=args.wl,
+ metrics=args.m, cputype=args.cputype)
+ ret = validator.test()
+
+ return ret
+
+
+if __name__ == "__main__":
+ import sys
+ sys.exit(main())
diff --git a/tools/perf/tests/shell/lib/perf_metric_validation_rules.json b/tools/perf/tests/shell/lib/perf_metric_validation_rules.json
new file mode 100644
index 000000000..eb6f59e01
--- /dev/null
+++ b/tools/perf/tests/shell/lib/perf_metric_validation_rules.json
@@ -0,0 +1,398 @@
+{
+ "SkipList": [
+ "tsx_aborted_cycles",
+ "tsx_transactional_cycles",
+ "C2_Pkg_Residency",
+ "C6_Pkg_Residency",
+ "C1_Core_Residency",
+ "C6_Core_Residency",
+ "tma_false_sharing",
+ "tma_remote_cache",
+ "tma_contested_accesses"
+ ],
+ "RelationshipRules": [
+ {
+ "RuleIndex": 1,
+ "Formula": "a+b",
+ "TestType": "RelationshipTest",
+ "RangeLower": "c",
+ "RangeUpper": "c",
+ "ErrorThreshold": 5.0,
+ "Description": "Intel(R) Optane(TM) Persistent Memory(PMEM) bandwidth total includes Intel(R) Optane(TM) Persistent Memory(PMEM) read bandwidth and Intel(R) Optane(TM) Persistent Memory(PMEM) write bandwidth",
+ "Metrics": [
+ {
+ "Name": "pmem_memory_bandwidth_read",
+ "Alias": "a"
+ },
+ {
+ "Name": "pmem_memory_bandwidth_write",
+ "Alias": "b"
+ },
+ {
+ "Name": "pmem_memory_bandwidth_total",
+ "Alias": "c"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 2,
+ "Formula": "a+b",
+ "TestType": "RelationshipTest",
+ "RangeLower": "c",
+ "RangeUpper": "c",
+ "ErrorThreshold": 5.0,
+ "Description": "DDR memory bandwidth total includes DDR memory read bandwidth and DDR memory write bandwidth",
+ "Metrics": [
+ {
+ "Name": "memory_bandwidth_read",
+ "Alias": "a"
+ },
+ {
+ "Name": "memory_bandwidth_write",
+ "Alias": "b"
+ },
+ {
+ "Name": "memory_bandwidth_total",
+ "Alias": "c"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 3,
+ "Formula": "a+b",
+ "TestType": "RelationshipTest",
+ "RangeLower": "100",
+ "RangeUpper": "100",
+ "ErrorThreshold": 5.0,
+ "Description": "Total memory read accesses includes memory reads from last level cache (LLC) addressed to local DRAM and memory reads from the last level cache (LLC) addressed to remote DRAM.",
+ "Metrics": [
+ {
+ "Name": "numa_reads_addressed_to_local_dram",
+ "Alias": "a"
+ },
+ {
+ "Name": "numa_reads_addressed_to_remote_dram",
+ "Alias": "b"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 4,
+ "Formula": "a",
+ "TestType": "SingleMetricTest",
+ "RangeLower": "0.125",
+ "RangeUpper": "",
+ "ErrorThreshold": "",
+ "Description": "",
+ "Metrics": [
+ {
+ "Name": "cpi",
+ "Alias": "a"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 5,
+ "Formula": "",
+ "TestType": "SingleMetricTest",
+ "RangeLower": "0",
+ "RangeUpper": "1",
+ "ErrorThreshold": 5.0,
+ "Description": "Ratio values should be within value range [0,1)",
+ "Metrics": [
+ {
+ "Name": "loads_per_instr",
+ "Alias": ""
+ },
+ {
+ "Name": "stores_per_instr",
+ "Alias": ""
+ },
+ {
+ "Name": "l1d_mpi",
+ "Alias": ""
+ },
+ {
+ "Name": "l1d_demand_data_read_hits_per_instr",
+ "Alias": ""
+ },
+ {
+ "Name": "l1_i_code_read_misses_with_prefetches_per_instr",
+ "Alias": ""
+ },
+ {
+ "Name": "l2_demand_data_read_hits_per_instr",
+ "Alias": ""
+ },
+ {
+ "Name": "l2_mpi",
+ "Alias": ""
+ },
+ {
+ "Name": "l2_demand_data_read_mpi",
+ "Alias": ""
+ },
+ {
+ "Name": "l2_demand_code_mpi",
+ "Alias": ""
+ }
+ ]
+ },
+ {
+ "RuleIndex": 6,
+ "Formula": "a+b+c+d",
+ "TestType": "RelationshipTest",
+ "RangeLower": "100",
+ "RangeUpper": "100",
+ "ErrorThreshold": 5.0,
+ "Description": "Sum of TMA level 1 metrics should be 100%",
+ "Metrics": [
+ {
+ "Name": "tma_frontend_bound",
+ "Alias": "a"
+ },
+ {
+ "Name": "tma_bad_speculation",
+ "Alias": "b"
+ },
+ {
+ "Name": "tma_backend_bound",
+ "Alias": "c"
+ },
+ {
+ "Name": "tma_retiring",
+ "Alias": "d"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 7,
+ "Formula": "a+b",
+ "TestType": "RelationshipTest",
+ "RangeLower": "c",
+ "RangeUpper": "c",
+ "ErrorThreshold": 5.0,
+ "Description": "Sum of the level 2 children should equal level 1 parent",
+ "Metrics": [
+ {
+ "Name": "tma_fetch_latency",
+ "Alias": "a"
+ },
+ {
+ "Name": "tma_fetch_bandwidth",
+ "Alias": "b"
+ },
+ {
+ "Name": "tma_frontend_bound",
+ "Alias": "c"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 8,
+ "Formula": "a+b",
+ "TestType": "RelationshipTest",
+ "RangeLower": "c",
+ "RangeUpper": "c",
+ "ErrorThreshold": 5.0,
+ "Description": "Sum of the level 2 children should equal level 1 parent",
+ "Metrics": [
+ {
+ "Name": "tma_branch_mispredicts",
+ "Alias": "a"
+ },
+ {
+ "Name": "tma_machine_clears",
+ "Alias": "b"
+ },
+ {
+ "Name": "tma_bad_speculation",
+ "Alias": "c"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 9,
+ "Formula": "a+b",
+ "TestType": "RelationshipTest",
+ "RangeLower": "c",
+ "RangeUpper": "c",
+ "ErrorThreshold": 5.0,
+ "Description": "Sum of the level 2 children should equal level 1 parent",
+ "Metrics": [
+ {
+ "Name": "tma_memory_bound",
+ "Alias": "a"
+ },
+ {
+ "Name": "tma_core_bound",
+ "Alias": "b"
+ },
+ {
+ "Name": "tma_backend_bound",
+ "Alias": "c"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 10,
+ "Formula": "a+b",
+ "TestType": "RelationshipTest",
+ "RangeLower": "c",
+ "RangeUpper": "c",
+ "ErrorThreshold": 5.0,
+ "Description": "Sum of the level 2 children should equal level 1 parent",
+ "Metrics": [
+ {
+ "Name": "tma_light_operations",
+ "Alias": "a"
+ },
+ {
+ "Name": "tma_heavy_operations",
+ "Alias": "b"
+ },
+ {
+ "Name": "tma_retiring",
+ "Alias": "c"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 11,
+ "Formula": "a+b+c",
+ "TestType": "RelationshipTest",
+ "RangeLower": "100",
+ "RangeUpper": "100",
+ "ErrorThreshold": 5.0,
+ "Description": "The all_requests includes the memory_page_empty, memory_page_misses, and memory_page_hits equals.",
+ "Metrics": [
+ {
+ "Name": "memory_page_empty_vs_all_requests",
+ "Alias": "a"
+ },
+ {
+ "Name": "memory_page_misses_vs_all_requests",
+ "Alias": "b"
+ },
+ {
+ "Name": "memory_page_hits_vs_all_requests",
+ "Alias": "c"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 12,
+ "Formula": "a-b",
+ "TestType": "RelationshipTest",
+ "RangeLower": "0",
+ "RangeUpper": "",
+ "ErrorThreshold": 5.0,
+ "Description": "CPU utilization in kernel mode should always be <= cpu utilization",
+ "Metrics": [
+ {
+ "Name": "cpu_utilization",
+ "Alias": "a"
+ },
+ {
+ "Name": "cpu_utilization_in_kernel_mode",
+ "Alias": "b"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 13,
+ "Formula": "a-b",
+ "TestType": "RelationshipTest",
+ "RangeLower": "0",
+ "RangeUpper": "",
+ "ErrorThreshold": 5.0,
+ "Description": "Total L2 misses per instruction should be >= L2 demand data read misses per instruction",
+ "Metrics": [
+ {
+ "Name": "l2_mpi",
+ "Alias": "a"
+ },
+ {
+ "Name": "l2_demand_data_read_mpi",
+ "Alias": "b"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 14,
+ "Formula": "a-b",
+ "TestType": "RelationshipTest",
+ "RangeLower": "0",
+ "RangeUpper": "",
+ "ErrorThreshold": 5.0,
+ "Description": "Total L2 misses per instruction should be >= L2 demand code misses per instruction",
+ "Metrics": [
+ {
+ "Name": "l2_mpi",
+ "Alias": "a"
+ },
+ {
+ "Name": "l2_demand_code_mpi",
+ "Alias": "b"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 15,
+ "Formula": "b+c+d",
+ "TestType": "RelationshipTest",
+ "RangeLower": "a",
+ "RangeUpper": "a",
+ "ErrorThreshold": 5.0,
+ "Description": "L3 data read, rfo, code misses per instruction equals total L3 misses per instruction.",
+ "Metrics": [
+ {
+ "Name": "llc_mpi",
+ "Alias": "a"
+ },
+ {
+ "Name": "llc_data_read_mpi_demand_plus_prefetch",
+ "Alias": "b"
+ },
+ {
+ "Name": "llc_rfo_read_mpi_demand_plus_prefetch",
+ "Alias": "c"
+ },
+ {
+ "Name": "llc_code_read_mpi_demand_plus_prefetch",
+ "Alias": "d"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 16,
+ "Formula": "a",
+ "TestType": "SingleMetricTest",
+ "RangeLower": "0",
+ "RangeUpper": "8",
+ "ErrorThreshold": 0.0,
+ "Description": "Setting generous range for allowable frequencies",
+ "Metrics": [
+ {
+ "Name": "uncore_freq",
+ "Alias": "a"
+ }
+ ]
+ },
+ {
+ "RuleIndex": 17,
+ "Formula": "a",
+ "TestType": "SingleMetricTest",
+ "RangeLower": "0",
+ "RangeUpper": "8",
+ "ErrorThreshold": 0.0,
+ "Description": "Setting generous range for allowable frequencies",
+ "Metrics": [
+ {
+ "Name": "cpu_operating_frequency",
+ "Alias": "a"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/tools/perf/tests/shell/lib/perf_record.sh b/tools/perf/tests/shell/lib/perf_record.sh
new file mode 100644
index 000000000..2b9e11b66
--- /dev/null
+++ b/tools/perf/tests/shell/lib/perf_record.sh
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0
+
+PERF_RECORD_LOGS=()
+
+perf_record_with_retry() {
+ local perfdata="$1"
+ local check_cmd="$2"
+ local testprog_base="$3"
+ shift 3
+
+ local logfile
+ logfile=$(mktemp /tmp/__perf_record_retry.XXXXXX)
+ PERF_RECORD_LOGS+=("$logfile")
+
+ # Save the e flag state and disable it
+ local save_e
+ if [[ $- == *e* ]]; then
+ save_e="set -e"
+ else
+ save_e="set +e"
+ fi
+ set +e
+
+ local duration
+ local first_run=true
+ local ret=1
+ local cmd_prefix="perf record"
+ if [ -n "${PERF_RECORD_CMD}" ]; then
+ cmd_prefix="${PERF_RECORD_CMD}"
+ fi
+
+ for duration in 0.01 0.1 0.3 1.0 2.0; do
+ rm -f "${perfdata}".old
+ ${cmd_prefix} "$@" -o "${perfdata}" ${testprog_base} ${duration} > "$logfile" 2>&1
+ local record_exit=$?
+
+ if [ "$first_run" = true ] && [ $record_exit -ne 0 ]; then
+ ret=2
+ break
+ fi
+ first_run=false
+
+ if [ -e "${perfdata}" ] && eval "${check_cmd}"; then
+ ret=0
+ break
+ fi
+ done
+
+ eval "$save_e"
+ return $ret
+}
+
+perf_record_cleanup() {
+ for logfile in "${PERF_RECORD_LOGS[@]}"; do
+ rm -f "$logfile"
+ done
+ PERF_RECORD_LOGS=()
+}
diff --git a/tools/perf/tests/shell/lib/probe.sh b/tools/perf/tests/shell/lib/probe.sh
new file mode 100644
index 000000000..5aa6e2ec5
--- /dev/null
+++ b/tools/perf/tests/shell/lib/probe.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Arnaldo Carvalho de Melo <acme@kernel.org>, 2017
+
+skip_if_no_perf_probe() {
+ perf probe 2>&1 | grep -q 'is not a perf-command' && return 2
+ return 0
+}
+
+skip_if_no_perf_trace() {
+ perf trace -h 2>&1 | grep -q -e 'is not a perf-command' -e 'trace command not available' && return 2
+ return 0
+}
diff --git a/tools/perf/tests/shell/lib/probe_vfs_getname.sh b/tools/perf/tests/shell/lib/probe_vfs_getname.sh
new file mode 100644
index 000000000..88cd0e26d
--- /dev/null
+++ b/tools/perf/tests/shell/lib/probe_vfs_getname.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# Arnaldo Carvalho de Melo <acme@kernel.org>, 2017
+
+perf probe -l 2>&1 | grep -q probe:vfs_getname
+had_vfs_getname=$?
+
+cleanup_probe_vfs_getname() {
+ if [ $had_vfs_getname -eq 1 ] ; then
+ perf probe -q -d probe:vfs_getname*
+ fi
+}
+
+add_probe_vfs_getname() {
+ add_probe_verbose=$1
+ if [ $had_vfs_getname -eq 1 ] ; then
+ result_initname_re="[[:space:]]+([[:digit:]]+)[[:space:]]+initname.*"
+ line=$(perf probe -L getname_flags 2>&1 | grep -E "$result_initname_re" | sed -r "s/$result_initname_re/\1/")
+
+ # Search the old regular expressions so that this will
+ # pass on older kernels as well.
+ if [ -z "$line" ] ; then
+ result_filename_re="[[:space:]]+([[:digit:]]+)[[:space:]]+result->uptr.*"
+ line=$(perf probe -L getname_flags 2>&1 | grep -E "$result_filename_re" | sed -r "s/$result_filename_re/\1/")
+ fi
+
+ if [ -z "$line" ] ; then
+ result_aname_re="[[:space:]]+([[:digit:]]+)[[:space:]]+result->aname = NULL;"
+ line=$(perf probe -L getname_flags 2>&1 | grep -E "$result_aname_re" | sed -r "s/$result_aname_re/\1/")
+ fi
+
+ if [ -z "$line" ] ; then
+ echo "Could not find probeable line"
+ return 2
+ fi
+
+ perf probe -q "vfs_getname=getname_flags:${line} pathname=result->name:string" || \
+ perf probe $add_probe_verbose "vfs_getname=getname_flags:${line} pathname=filename:ustring" || return 1
+ fi
+}
+
+skip_if_no_debuginfo() {
+ add_probe_vfs_getname -v 2>&1 | grep -E -q "^(Failed to find the path for the kernel|Debuginfo-analysis is not supported)|(file has no debug information)" && return 2
+ return 1
+}
+
+# check if perf is compiled with libtraceevent support
+skip_no_probe_record_support() {
+ if [ $had_vfs_getname -eq 1 ] ; then
+ perf check feature -q libtraceevent && return 1
+ return 2
+ fi
+}
diff --git a/tools/perf/tests/shell/lib/setup_python.sh b/tools/perf/tests/shell/lib/setup_python.sh
new file mode 100644
index 000000000..2173215a0
--- /dev/null
+++ b/tools/perf/tests/shell/lib/setup_python.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+if [ "x$PYTHON" = "x" ]
+then
+ python3 --version >/dev/null 2>&1 && PYTHON=python3
+fi
+if [ "x$PYTHON" = "x" ]
+then
+ python --version >/dev/null 2>&1 && PYTHON=python
+fi
+if [ "x$PYTHON" = "x" ]
+then
+ echo Skipping test, python not detected please set environment variable PYTHON.
+ exit 2
+fi
+
+# Set PYTHONPATH to find the in-tree built perf.so first, avoiding system-wide perf.so
+if [ -n "$PERF_EXEC_PATH" ] && [ -d "$PERF_EXEC_PATH/python" ]; then
+ PYTHONPATH_DIR="$PERF_EXEC_PATH/python"
+elif [ -d "$(dirname "$0")/../../python" ]; then
+ PYTHONPATH_DIR="$(dirname "$0")/../../python"
+elif [ -d "$(dirname "$0")/../python" ]; then
+ PYTHONPATH_DIR="$(dirname "$0")/../python"
+fi
+
+if [ -n "$PYTHONPATH_DIR" ]; then
+ export PYTHONPATH="$PYTHONPATH_DIR${PYTHONPATH:+:$PYTHONPATH}"
+fi
diff --git a/tools/perf/tests/shell/lib/stat_output.sh b/tools/perf/tests/shell/lib/stat_output.sh
new file mode 100644
index 000000000..3c36e80fe
--- /dev/null
+++ b/tools/perf/tests/shell/lib/stat_output.sh
@@ -0,0 +1,195 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+# Return true if perf_event_paranoid is > $1 and not running as root.
+function ParanoidAndNotRoot()
+{
+ [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
+}
+
+# $1 name $2 extra_opt
+check_no_args()
+{
+ echo -n "Checking $1 output: no args "
+ perf stat $2 true
+ commachecker --no-args
+ echo "[Success]"
+}
+
+check_system_wide()
+{
+ echo -n "Checking $1 output: system wide "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoid and not root"
+ return
+ fi
+ perf stat -a $2 true
+ commachecker --system-wide
+ echo "[Success]"
+}
+
+check_system_wide_no_aggr()
+{
+ echo -n "Checking $1 output: system wide no aggregation "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoid and not root"
+ return
+ fi
+ perf stat -A -a --no-merge $2 true
+ commachecker --system-wide-no-aggr
+ echo "[Success]"
+}
+
+check_interval()
+{
+ echo -n "Checking $1 output: interval "
+ perf stat -I 1000 $2 true
+ commachecker --interval
+ echo "[Success]"
+}
+
+check_event()
+{
+ echo -n "Checking $1 output: event "
+ perf stat -e cpu-clock $2 true
+ commachecker --event
+ echo "[Success]"
+}
+
+check_per_core()
+{
+ echo -n "Checking $1 output: per core "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoid and not root"
+ return
+ fi
+ perf stat --per-core -a $2 true
+ commachecker --per-core
+ echo "[Success]"
+}
+
+check_per_thread()
+{
+ echo -n "Checking $1 output: per thread "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoid and not root"
+ return
+ fi
+ perf stat --per-thread -p $$ $2 true
+ commachecker --per-thread
+ echo "[Success]"
+}
+
+check_per_cache_instance()
+{
+ echo -n "Checking $1 output: per cache instance "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoid and not root"
+ return
+ fi
+ perf stat --per-cache -a $2 true
+ commachecker --per-cache
+ echo "[Success]"
+}
+
+check_per_cluster()
+{
+ echo -n "Checking $1 output: per cluster "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoid and not root"
+ return
+ fi
+ perf stat --per-cluster -a $2 true
+ echo "[Success]"
+}
+
+check_per_die()
+{
+ echo -n "Checking $1 output: per die "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoid and not root"
+ return
+ fi
+ perf stat --per-die -a $2 true
+ commachecker --per-die
+ echo "[Success]"
+}
+
+check_per_node()
+{
+ echo -n "Checking $1 output: per node "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoid and not root"
+ return
+ fi
+ perf stat --per-node -a $2 true
+ commachecker --per-node
+ echo "[Success]"
+}
+
+check_per_socket()
+{
+ echo -n "Checking $1 output: per socket "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoid and not root"
+ return
+ fi
+ perf stat --per-socket -a $2 true
+ commachecker --per-socket
+ echo "[Success]"
+}
+
+check_metric_only()
+{
+ echo -n "Checking $1 output: metric only "
+ if [ "$(uname -m)" = "s390x" ] && ! grep '^facilities' /proc/cpuinfo | grep -qw 67
+ then
+ echo "[Skip] CPU-measurement counter facility not installed"
+ return
+ fi
+ perf stat --metric-only $2 -M page_faults_per_second true
+ commachecker --metric-only
+ echo "[Success]"
+}
+
+# The perf stat options for per-socket, per-core, per-die
+# and -A ( no_aggr mode ) uses the info fetched from this
+# directory: "/sys/devices/system/cpu/cpu*/topology". For
+# example, socket value is fetched from "physical_package_id"
+# file in topology directory.
+# Reference: cpu__get_topology_int in util/cpumap.c
+# If the platform doesn't expose topology information, values
+# will be set to -1. For example, incase of pSeries platform
+# of powerpc, value for "physical_package_id" is restricted
+# and set to -1. Check here validates the socket-id read from
+# topology file before proceeding further
+
+FILE_LOC="/sys/devices/system/cpu/cpu*/topology/"
+FILE_NAME="physical_package_id"
+
+function check_for_topology()
+{
+ if ! ParanoidAndNotRoot 0
+ then
+ socket_file=`ls $FILE_LOC/$FILE_NAME | head -n 1`
+ [ -z $socket_file ] && {
+ echo 0
+ return
+ }
+ socket_id=`cat $socket_file`
+ [ $socket_id == -1 ] && {
+ echo 1
+ return
+ }
+ fi
+ echo 0
+}
diff --git a/tools/perf/tests/shell/lib/waiting.sh b/tools/perf/tests/shell/lib/waiting.sh
new file mode 100644
index 000000000..3a152892e
--- /dev/null
+++ b/tools/perf/tests/shell/lib/waiting.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+tenths=date\ +%s%1N
+
+# Wait for PID $1 to have $2 number of threads started
+# Time out after $3 tenths of a second or 5 seconds if $3 is ""
+wait_for_threads()
+{
+ tm_out=$3 ; [ -n "${tm_out}" ] || tm_out=50
+ start_time=$($tenths)
+ while [ -e "/proc/$1/task" ] ; do
+ th_cnt=$(find "/proc/$1/task" -mindepth 1 -maxdepth 1 -printf x | wc -c)
+ if [ "${th_cnt}" -ge "$2" ] ; then
+ return 0
+ fi
+ # Wait at most tm_out tenths of a second
+ if [ $(($($tenths) - start_time)) -ge $tm_out ] ; then
+ echo "PID $1 does not have $2 threads"
+ return 1
+ fi
+ done
+ return 1
+}
+
+# Wait for perf record -vvv 2>$2 with PID $1 to start by looking at file $2
+# It depends on capturing perf record debug message "perf record has started"
+# Time out after $3 tenths of a second or 5 seconds if $3 is ""
+wait_for_perf_to_start()
+{
+ tm_out=$3 ; [ -n "${tm_out}" ] || tm_out=50
+ echo "Waiting for \"perf record has started\" message"
+ start_time=$($tenths)
+ while [ -e "/proc/$1" ] ; do
+ if grep -q "perf record has started" "$2" ; then
+ echo OK
+ break
+ fi
+ # Wait at most tm_out tenths of a second
+ if [ $(($($tenths) - start_time)) -ge $tm_out ] ; then
+ echo "perf recording did not start"
+ return 1
+ fi
+ done
+ return 0
+}
+
+# Wait for process PID %1 to exit
+# Time out after $2 tenths of a second or 5 seconds if $2 is ""
+wait_for_process_to_exit()
+{
+ tm_out=$2 ; [ -n "${tm_out}" ] || tm_out=50
+ start_time=$($tenths)
+ while [ -e "/proc/$1" ] ; do
+ # Wait at most tm_out tenths of a second
+ if [ $(($($tenths) - start_time)) -ge $tm_out ] ; then
+ echo "PID $1 did not exit as expected"
+ return 1
+ fi
+ done
+ return 0
+}
+
+# Check if PID $1 is still running after $2 tenths of a second
+# or 0.3 seconds if $2 is ""
+is_running()
+{
+ tm_out=$2 ; [ -n "${tm_out}" ] || tm_out=3
+ start_time=$($tenths)
+ while [ -e "/proc/$1" ] ; do
+ # Check for at least tm_out tenths of a second
+ if [ $(($($tenths) - start_time)) -gt $tm_out ] ; then
+ return 0
+ fi
+ done
+ echo "PID $1 exited prematurely"
+ return 1
+}
diff --git a/tools/perf/tests/shell/list.sh b/tools/perf/tests/shell/list.sh
new file mode 100755
index 000000000..0c04b3159
--- /dev/null
+++ b/tools/perf/tests/shell/list.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# perf list tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/setup_python.sh
+. "${shelldir}"/lib/setup_python.sh
+
+list_output=$(mktemp /tmp/__perf_test.list_output.json.XXXXX)
+
+cleanup() {
+ rm -f "${list_output}"
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_list_json() {
+ echo "Json output test"
+ # Generate perf list json output into list_output file.
+ perf list -j -o "${list_output}"
+ # Validate the json using python, redirect the json copy to /dev/null as
+ # otherwise the test may block writing to stdout.
+ $PYTHON -m json.tool "${list_output}" /dev/null
+ echo "Json output test [Success]"
+}
+
+test_list_json
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/lock_contention.sh b/tools/perf/tests/shell/lock_contention.sh
new file mode 100755
index 000000000..5df9a0cd4
--- /dev/null
+++ b/tools/perf/tests/shell/lock_contention.sh
@@ -0,0 +1,363 @@
+#!/bin/bash
+# kernel lock contention analysis test
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+result=$(mktemp /tmp/__perf_test.result.XXXXX)
+errout=$(mktemp /tmp/__perf_test.errout.XXXXX)
+
+# Workload to generate lock contention.
+# Using 1 group (-g 1) keeps runtime low while generating sufficient lock events.
+# We include -p (pipes) because socketpairs don't generate enough lock events on s390.
+msg_workload="perf bench sched messaging -g 1 -p"
+
+cleanup() {
+ rm -f ${perfdata}
+ rm -f ${result}
+ rm -f ${errout}
+ trap - EXIT TERM INT ERR
+}
+
+trap_cleanup() {
+ if (( $? == 139 )); then #SIGSEGV
+ err=1
+ fi
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit ${err}
+}
+trap trap_cleanup EXIT TERM INT ERR
+
+check() {
+ if [ "$(id -u)" != 0 ]; then
+ echo "[Skip] No root permission"
+ err=2
+ exit
+ fi
+
+ if ! perf list tracepoint | grep -q lock:contention_begin; then
+ echo "[Skip] No lock contention tracepoints"
+ err=2
+ exit
+ fi
+
+ # shellcheck disable=SC2046
+ if [ `nproc` -lt 4 ]; then
+ echo "[Skip] Low number of CPUs (`nproc`), lock event cannot be triggered certainly"
+ err=2
+ exit
+ fi
+}
+
+test_record()
+{
+ echo "Testing perf lock record and perf lock contention"
+ perf lock record -o ${perfdata} -- ${msg_workload} > /dev/null 2>&1
+ # the output goes to the stderr and we expect only 1 output (-E 1)
+ perf lock contention -i ${perfdata} -E 1 -q 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] Recorded result count is not 1:" "$(cat "${result}" | wc -l)"
+ err=1
+ exit
+ fi
+}
+
+test_bpf()
+{
+ echo "Testing perf lock contention --use-bpf"
+
+ if ! perf lock con -b true > /dev/null 2>&1 ; then
+ echo "[Skip] No BPF support"
+ return
+ fi
+
+ # the perf lock contention output goes to the stderr
+ perf lock con -a -b -E 1 -q -- ${msg_workload} > /dev/null 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] BPF result count is not 1:" "$(cat "${result}" | wc -l)"
+ err=1
+ exit
+ fi
+}
+
+test_record_concurrent()
+{
+ echo "Testing perf lock record and perf lock contention at the same time"
+ perf lock record -o- -- ${msg_workload} 2> ${errout} | \
+ perf lock contention -i- -E 1 -q 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] Recorded result count is not 1:" "$(cat "${result}" | wc -l)"
+ cat ${errout}
+ cat ${result}
+ err=1
+ exit
+ fi
+}
+
+test_aggr_task()
+{
+ echo "Testing perf lock contention --threads"
+ perf lock contention -i ${perfdata} -t -E 1 -q 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] Recorded result count is not 1:" "$(cat "${result}" | wc -l)"
+ err=1
+ exit
+ fi
+
+ if ! perf lock con -b true > /dev/null 2>&1 ; then
+ return
+ fi
+
+ # the perf lock contention output goes to the stderr
+ perf lock con -a -b -t -E 1 -q -- ${msg_workload} > /dev/null 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] BPF result count is not 1:" "$(cat "${result}" | wc -l)"
+ err=1
+ exit
+ fi
+}
+
+test_aggr_addr()
+{
+ echo "Testing perf lock contention --lock-addr"
+ perf lock contention -i ${perfdata} -l -E 1 -q 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] Recorded result count is not 1:" "$(cat "${result}" | wc -l)"
+ err=1
+ exit
+ fi
+
+ if ! perf lock con -b true > /dev/null 2>&1 ; then
+ return
+ fi
+
+ # the perf lock contention output goes to the stderr
+ perf lock con -a -b -l -E 1 -q -- ${msg_workload} > /dev/null 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] BPF result count is not 1:" "$(cat "${result}" | wc -l)"
+ err=1
+ exit
+ fi
+}
+
+test_aggr_cgroup()
+{
+ echo "Testing perf lock contention --lock-cgroup"
+
+ if ! perf lock con -b true > /dev/null 2>&1 ; then
+ echo "[Skip] No BPF support"
+ return
+ fi
+
+ # the perf lock contention output goes to the stderr
+ perf lock con -a -b --lock-cgroup -E 1 -q -- ${msg_workload} > /dev/null 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] BPF result count is not 1:" "$(cat "${result}" | wc -l)"
+ err=1
+ exit
+ fi
+}
+
+test_type_filter()
+{
+ echo "Testing perf lock contention --type-filter (w/ spinlock)"
+ perf lock contention -i ${perfdata} -Y spinlock -q 2> ${result}
+ if [ "$(grep -c -v spinlock "${result}")" != "0" ]; then
+ echo "[Fail] Recorded result should not have non-spinlocks:" "$(cat "${result}")"
+ err=1
+ exit
+ fi
+
+ if ! perf lock con -b true > /dev/null 2>&1 ; then
+ return
+ fi
+
+ perf lock con -a -b -Y spinlock -q -- ${msg_workload} > /dev/null 2> ${result}
+ if [ "$(grep -c -v spinlock "${result}")" != "0" ]; then
+ echo "[Fail] BPF result should not have non-spinlocks:" "$(cat "${result}")"
+ err=1
+ exit
+ fi
+}
+
+test_lock_filter()
+{
+ echo "Testing perf lock contention --lock-filter (w/ tasklist_lock)"
+ perf lock contention -i ${perfdata} -l -q 2> ${result}
+ if [ "$(grep -c tasklist_lock "${result}")" != "1" ]; then
+ echo "[Skip] Could not find 'tasklist_lock'"
+ return
+ fi
+
+ perf lock contention -i ${perfdata} -L tasklist_lock -q 2> ${result}
+
+ # find out the type of tasklist_lock
+ test_lock_filter_type=$(head -1 "${result}" | awk '{ print $8 }' | sed -e 's/:.*//')
+
+ if [ "$(grep -c -v "${test_lock_filter_type}" "${result}")" != "0" ]; then
+ echo "[Fail] Recorded result should not have non-${test_lock_filter_type} locks:" "$(cat "${result}")"
+ err=1
+ exit
+ fi
+
+ if ! perf lock con -b true > /dev/null 2>&1 ; then
+ return
+ fi
+
+ perf lock con -a -b -L tasklist_lock -q -- ${msg_workload} > /dev/null 2> ${result}
+ if [ "$(grep -c -v "${test_lock_filter_type}" "${result}")" != "0" ]; then
+ echo "[Fail] BPF result should not have non-${test_lock_filter_type} locks:" "$(cat "${result}")"
+ err=1
+ exit
+ fi
+
+ perf lock con -b -L mmap_lock -q -- perf bench mem mmap -t 2 -l 10 > /dev/null 2> ${result}
+
+ # find out the type of mmap_lock
+ test_lock_filter_type=$(head -1 "${result}" | awk '{ print $8 }' | sed -e 's/:.*//')
+
+ if [ "$(grep -c -v "${test_lock_filter_type}" "${result}")" != "0" ]; then
+ echo "[Fail] BPF result should not have non-${test_lock_filter_type} locks:" "$(cat "${result}")"
+ err=1
+ exit
+ fi
+}
+
+test_stack_filter()
+{
+ echo "Testing perf lock contention --callstack-filter (w/ unix_stream)"
+ perf lock contention -i ${perfdata} -v -q 2> ${result}
+ if [ "$(grep -c unix_stream "${result}")" = "0" ]; then
+ echo "[Skip] Could not find 'unix_stream'"
+ return
+ fi
+
+ perf lock contention -i ${perfdata} -E 1 -S unix_stream -q 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] Recorded result should have a lock from unix_stream:" "$(cat "${result}")"
+ err=1
+ exit
+ fi
+
+ if ! perf lock con -b true > /dev/null 2>&1 ; then
+ return
+ fi
+
+ perf lock con -a -b -S unix_stream -E 1 -q -- ${msg_workload} > /dev/null 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] BPF result should have a lock from unix_stream:" "$(cat "${result}")"
+ err=1
+ exit
+ fi
+}
+
+test_aggr_task_stack_filter()
+{
+ echo "Testing perf lock contention --callstack-filter with task aggregation"
+ perf lock contention -i ${perfdata} -v -q 2> ${result}
+ if [ "$(grep -c unix_stream "${result}")" = "0" ]; then
+ echo "[Skip] Could not find 'unix_stream'"
+ return
+ fi
+
+ perf lock contention -i ${perfdata} -t -E 1 -S unix_stream -q 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] Recorded result should have a task from unix_stream:" "$(cat "${result}")"
+ err=1
+ exit
+ fi
+
+ if ! perf lock con -b true > /dev/null 2>&1 ; then
+ return
+ fi
+
+ perf lock con -a -b -t -S unix_stream -E 1 -q -- ${msg_workload} > /dev/null 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] BPF result should have a task from unix_stream:" "$(cat "${result}")"
+ err=1
+ exit
+ fi
+}
+test_cgroup_filter()
+{
+ echo "Testing perf lock contention --cgroup-filter"
+
+ if ! perf lock con -b true > /dev/null 2>&1 ; then
+ echo "[Skip] No BPF support"
+ return
+ fi
+
+ perf lock con -a -b --lock-cgroup -E 1 -F wait_total -q \
+ -- ${msg_workload} > /dev/null 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] BPF result should have a cgroup result:" "$(cat "${result}")"
+ err=1
+ exit
+ fi
+
+ cgroup=$(cat "${result}" | awk '{ print $3 }')
+ perf lock con -a -b --lock-cgroup -E 1 -G "${cgroup}" -q \
+ -- ${msg_workload} > /dev/null 2> ${result}
+ if [ "$(cat "${result}" | wc -l)" != "1" ]; then
+ echo "[Fail] BPF result should have a result with cgroup filter:" "$(cat "${cgroup}")"
+ err=1
+ exit
+ fi
+}
+
+
+test_csv_output()
+{
+ echo "Testing perf lock contention CSV output"
+ perf lock contention -i ${perfdata} -E 1 -x , --output ${result}
+ # count the number of commas in the header
+ # it should have 5: contended, total-wait, max-wait, avg-wait, type, caller
+ header=$(grep "# output:" ${result} | tr -d -c , | wc -c)
+ if [ "${header}" != "5" ]; then
+ echo "[Fail] Recorded result does not have enough output columns: ${header} != 5"
+ err=1
+ exit
+ fi
+ # count the number of commas in the output
+ output=$(grep -v "^#" ${result} | tr -d -c , | wc -c)
+ if [ "${header}" != "${output}" ]; then
+ echo "[Fail] Recorded result does not match the number of commas: ${header} != ${output}"
+ err=1
+ exit
+ fi
+
+ if ! perf lock con -b true > /dev/null 2>&1 ; then
+ echo "[Skip] No BPF support"
+ return
+ fi
+
+ # the perf lock contention output goes to the stderr
+ perf lock con -a -b -E 1 -x , --output ${result} -- ${msg_workload} > /dev/null 2>&1
+ output=$(grep -v "^#" ${result} | tr -d -c , | wc -c)
+ if [ "${header}" != "${output}" ]; then
+ echo "[Fail] BPF result does not match the number of commas: ${header} != ${output}"
+ err=1
+ exit
+ fi
+}
+
+check
+
+test_record
+test_bpf
+test_record_concurrent
+test_aggr_task
+test_aggr_addr
+test_aggr_cgroup
+test_type_filter
+test_lock_filter
+test_stack_filter
+test_aggr_task_stack_filter
+test_cgroup_filter
+test_csv_output
+
+cleanup
+exit ${err}
diff --git a/tools/perf/tests/shell/perf-report-hierarchy.sh b/tools/perf/tests/shell/perf-report-hierarchy.sh
new file mode 100755
index 000000000..e3c6f9a24
--- /dev/null
+++ b/tools/perf/tests/shell/perf-report-hierarchy.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+# perf report --hierarchy
+# SPDX-License-Identifier: GPL-2.0
+# Arnaldo Carvalho de Melo <acme@redhat.com>
+
+set -e
+
+temp_dir=$(mktemp -d /tmp/perf-test-report.XXXXXXXXXX)
+
+cleanup()
+{
+ trap - EXIT TERM INT
+ sane=$(echo "${temp_dir}" | cut -b 1-21)
+ if [ "${sane}" = "/tmp/perf-test-report" ] ; then
+ echo "--- Cleaning up ---"
+ rm -rf "${temp_dir:?}/"*
+ rmdir "${temp_dir}"
+ fi
+}
+
+trap_cleanup()
+{
+ cleanup
+ exit 1
+}
+
+trap trap_cleanup EXIT TERM INT
+
+test_report_hierarchy()
+{
+ echo "perf report --hierarchy"
+
+ perf_data="${temp_dir}/perf-report-hierarchy-perf.data"
+ perf record -o "${perf_data}" uname
+ perf report --hierarchy -i "${perf_data}" > /dev/null
+ echo "perf report --hierarchy test [Success]"
+}
+
+test_report_hierarchy
+
+cleanup
+
+exit 0
diff --git a/tools/perf/tests/shell/perf_sched_stats.sh b/tools/perf/tests/shell/perf_sched_stats.sh
new file mode 100755
index 000000000..f13eb0a75
--- /dev/null
+++ b/tools/perf/tests/shell/perf_sched_stats.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+# perf sched stats tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+if [ "$(id -u)" != 0 ]; then
+ echo "[Skip] No root permission"
+ exit 2
+fi
+
+perfdata=$(mktemp /tmp/__perf_test_sched_stats.perf.data.XXXXX)
+perfdata2=$(mktemp /tmp/__perf_test_sched_stats.perf.data.XXXXX)
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${perfdata}".old
+ rm -f "${perfdata2}"
+ rm -f "${perfdata2}".old
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+err=0
+test_perf_sched_stats_record() {
+ echo "Basic perf sched stats record test"
+ if ! perf sched stats record -o "${perfdata}" true 2>&1 | \
+ grep -E -q "[ perf sched stats: Wrote samples to perf.data ]"
+ then
+ echo "Basic perf sched stats record test [Failed]"
+ err=1
+ return
+ fi
+ echo "Basic perf sched stats record test [Success]"
+}
+
+test_perf_sched_stats_report() {
+ echo "Basic perf sched stats report test"
+ perf sched stats record -o "${perfdata}" true > /dev/null
+ if ! perf sched stats report -i "${perfdata}" 2>&1 | grep -E -q "Description"
+ then
+ echo "Basic perf sched stats report test [Failed]"
+ err=1
+ return
+ fi
+ echo "Basic perf sched stats report test [Success]"
+}
+
+test_perf_sched_stats_live() {
+ echo "Basic perf sched stats live mode test"
+ if ! perf sched stats true 2>&1 | grep -E -q "Description"
+ then
+ echo "Basic perf sched stats live mode test [Failed]"
+ err=1
+ return
+ fi
+ echo "Basic perf sched stats live mode test [Success]"
+}
+
+test_perf_sched_stats_diff() {
+ echo "Basic perf sched stats diff test"
+ perf sched stats record -o "${perfdata}" true > /dev/null
+ perf sched stats record -o "${perfdata2}" true > /dev/null
+ if ! perf sched stats diff "${perfdata}" "${perfdata2}" > /dev/null
+ then
+ echo "Basic perf sched stats diff test [Failed]"
+ err=1
+ return
+ fi
+ echo "Basic perf sched stats diff test [Success]"
+}
+
+test_perf_sched_stats_record
+test_perf_sched_stats_report
+test_perf_sched_stats_live
+test_perf_sched_stats_diff
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/perftool-testsuite_probe.sh b/tools/perf/tests/shell/perftool-testsuite_probe.sh
new file mode 100755
index 000000000..3863df16c
--- /dev/null
+++ b/tools/perf/tests/shell/perftool-testsuite_probe.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# perftool-testsuite_probe (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+[ "$(id -u)" = 0 ] || exit 2
+test -d "$(dirname "$0")/base_probe" || exit 2
+cd "$(dirname "$0")/base_probe" || exit 2
+status=0
+
+PERFSUITE_RUN_DIR=$(mktemp -d /tmp/"$(basename "$0" .sh)".XXX)
+export PERFSUITE_RUN_DIR
+
+for testcase in setup.sh test_*; do # skip setup.sh if not present or not executable
+ test -x "$testcase" || continue
+ ./"$testcase"
+ (( status += $? ))
+done
+
+if ! [ "$PERFTEST_KEEP_LOGS" = "y" ]; then
+ rm -rf "$PERFSUITE_RUN_DIR"
+fi
+
+test $status -ne 0 && exit 1
+exit 0
diff --git a/tools/perf/tests/shell/perftool-testsuite_report.sh b/tools/perf/tests/shell/perftool-testsuite_report.sh
new file mode 100755
index 000000000..a8cf75b4e
--- /dev/null
+++ b/tools/perf/tests/shell/perftool-testsuite_report.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+# perftool-testsuite_report (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+test -d "$(dirname "$0")/base_report" || exit 2
+cd "$(dirname "$0")/base_report" || exit 2
+status=0
+
+PERFSUITE_RUN_DIR=$(mktemp -d /tmp/"$(basename "$0" .sh)".XXX)
+export PERFSUITE_RUN_DIR
+
+for testcase in setup.sh test_*; do # skip setup.sh if not present or not executable
+ test -x "$testcase" || continue
+ ./"$testcase"
+ (( status += $? ))
+done
+
+if ! [ "$PERFTEST_KEEP_LOGS" = "y" ]; then
+ rm -rf "$PERFSUITE_RUN_DIR"
+fi
+
+test $status -ne 0 && exit 1
+exit 0
diff --git a/tools/perf/tests/shell/pipe_test.sh b/tools/perf/tests/shell/pipe_test.sh
new file mode 100755
index 000000000..ce68d850c
--- /dev/null
+++ b/tools/perf/tests/shell/pipe_test.sh
@@ -0,0 +1,127 @@
+#!/bin/bash
+# perf pipe recording and injection test
+# SPDX-License-Identifier: GPL-2.0
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+sym="noploop"
+
+skip_test_missing_symbol ${sym}
+
+data=$(mktemp /tmp/perf.data.XXXXXX)
+data2=$(mktemp /tmp/perf.data2.XXXXXX)
+prog="perf test -w noploop 0.1"
+[ "$(uname -m)" = "s390x" ] && prog="perf test -w noploop 3"
+err=0
+
+set -e
+
+cleanup() {
+ rm -rf "${data}"
+ rm -rf "${data}".old
+ rm -rf "${data2}"
+ rm -rf "${data2}".old
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_record_report() {
+ echo
+ echo "Record+report pipe test"
+
+ task="perf"
+ if ! perf record -e task-clock:u -o - ${prog} | perf report -i - --task | grep -q ${task}
+ then
+ echo "Record+report pipe test [Failed - cannot find the test file in the perf report #1]"
+ err=1
+ return
+ fi
+
+ if ! perf record -g -e task-clock:u -o - ${prog} | perf report -i - --task | grep -q ${task}
+ then
+ echo "Record+report pipe test [Failed - cannot find the test file in the perf report #2]"
+ err=1
+ return
+ fi
+
+ perf record -g -e task-clock:u -o - ${prog} > ${data}
+ if ! perf report -i ${data} --task | grep -q ${task}
+ then
+ echo "Record+report pipe test [Failed - cannot find the test file in the perf report #3]"
+ err=1
+ return
+ fi
+
+ echo "Record+report pipe test [Success]"
+}
+
+test_inject_bids() {
+ inject_opt=$1
+
+ echo
+ echo "Inject ${inject_opt} build-ids test"
+
+ if ! perf record -e task-clock:u -o - ${prog} | perf inject ${inject_opt}| perf report -i - | grep -q ${sym}
+ then
+ echo "Inject build-ids test [Failed - cannot find noploop function in pipe #1]"
+ err=1
+ return
+ fi
+
+ if ! perf record -g -e task-clock:u -o - ${prog} | perf inject ${inject_opt} | perf report -i - | grep -q ${sym}
+ then
+ echo "Inject ${inject_opt} build-ids test [Failed - cannot find noploop function in pipe #2]"
+ err=1
+ return
+ fi
+
+ perf record -e task-clock:u -o - ${prog} | perf inject ${inject_opt} -o ${data}
+ if ! perf report -i ${data} | grep -q ${sym}; then
+ echo "Inject ${inject_opt} build-ids test [Failed - cannot find noploop function in pipe #3]"
+ err=1
+ return
+ fi
+
+ perf record -e task-clock:u -o ${data} ${prog}
+ if ! perf inject ${inject_opt} -i ${data} | perf report -i - | grep -q ${sym}; then
+ echo "Inject ${inject_opt} build-ids test [Failed - cannot find noploop function in pipe #4]"
+ err=1
+ return
+ fi
+
+ perf record -e task-clock:u -o - ${prog} > ${data}
+ if ! perf inject ${inject_opt} -i ${data} | perf report -i - | grep -q ${sym}; then
+ echo "Inject ${inject_opt} build-ids test [Failed - cannot find noploop function in pipe #5]"
+ err=1
+ return
+ fi
+
+ perf record -e task-clock:u -o - ${prog} > ${data}
+ perf inject ${inject_opt} -i ${data} -o ${data2}
+ if ! perf report -i ${data2} | grep -q ${sym}; then
+ echo "Inject ${inject_opt} build-ids test [Failed - cannot find noploop function in pipe #6]"
+ err=1
+ return
+ fi
+
+ echo "Inject ${inject_opt} build-ids test [Success]"
+}
+
+test_record_report
+test_inject_bids -B
+test_inject_bids -b
+test_inject_bids --buildid-all
+test_inject_bids --mmap2-buildid-all
+
+cleanup
+exit $err
+
diff --git a/tools/perf/tests/shell/probe_vfs_getname.sh b/tools/perf/tests/shell/probe_vfs_getname.sh
new file mode 100755
index 000000000..5fe5682c2
--- /dev/null
+++ b/tools/perf/tests/shell/probe_vfs_getname.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# Add vfs_getname probe to get syscall args filenames (exclusive)
+
+# SPDX-License-Identifier: GPL-2.0
+# Arnaldo Carvalho de Melo <acme@kernel.org>, 2017
+
+# shellcheck source=lib/probe.sh
+. "$(dirname $0)"/lib/probe.sh
+
+skip_if_no_perf_probe || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+# shellcheck source=lib/probe_vfs_getname.sh
+. "$(dirname $0)"/lib/probe_vfs_getname.sh
+
+add_probe_vfs_getname
+err=$?
+
+if [ $err -eq 1 ] ; then
+ skip_if_no_debuginfo
+ err=$?
+fi
+
+cleanup_probe_vfs_getname
+exit $err
diff --git a/tools/perf/tests/shell/python-use.sh b/tools/perf/tests/shell/python-use.sh
new file mode 100755
index 000000000..fd2ee5390
--- /dev/null
+++ b/tools/perf/tests/shell/python-use.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+# 'import perf' in python
+# SPDX-License-Identifier: GPL-2.0
+# Just test if we can load the python binding.
+set -e
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/setup_python.sh
+. "${shelldir}"/lib/setup_python.sh
+
+MODULE_DIR=$(dirname "$(which perf)")/python
+
+if [ -d "$MODULE_DIR" ]
+then
+ CMD=$(cat <<EOF
+import sys
+sys.path.insert(0, '$MODULE_DIR')
+import perf
+print('success!')
+EOF
+ )
+else
+ CMD=$(cat <<EOF
+import perf
+print('success!')
+EOF
+ )
+fi
+
+echo -e "Testing 'import perf' with:\n$CMD"
+
+if ! echo "$CMD" | $PYTHON | grep -q "success!"
+then
+ exit 1
+fi
+exit 0
diff --git a/tools/perf/tests/shell/record+probe_libc_inet_pton.sh b/tools/perf/tests/shell/record+probe_libc_inet_pton.sh
new file mode 100755
index 000000000..eca629ee8
--- /dev/null
+++ b/tools/perf/tests/shell/record+probe_libc_inet_pton.sh
@@ -0,0 +1,117 @@
+#!/bin/bash
+# probe libc's inet_pton & backtrace it with ping (exclusive)
+
+# Installs a probe on libc's inet_pton function, that will use uprobes,
+# then use 'perf trace' on a ping to localhost asking for just one packet
+# with the a backtrace 3 levels deep, check that it is what we expect.
+# This needs no debuginfo package, all is done using the libc ELF symtab
+# and the CFI info in the binaries.
+
+# SPDX-License-Identifier: GPL-2.0
+# Arnaldo Carvalho de Melo <acme@kernel.org>, 2017
+
+# shellcheck source=lib/probe.sh
+. "$(dirname "$0")/lib/probe.sh"
+# shellcheck source=lib/probe_vfs_getname.sh
+. "$(dirname "$0")/lib/probe_vfs_getname.sh"
+
+libc=$(grep -w libc /proc/self/maps | head -1 | sed -r 's/.*[[:space:]](\/.*)/\1/g')
+nm -Dg $libc 2>/dev/null | grep -F -q inet_pton || exit 254
+
+event_pattern='probe_libc:inet_pton(_[[:digit:]]+)?'
+
+add_libc_inet_pton_event() {
+
+ event_name=$(perf probe -f -x $libc -a inet_pton 2>&1 | \
+ awk -v ep="$event_pattern" -v l="$libc" '$0 ~ ep && $0 ~ \
+ ("\\(on inet_pton in " l "\\)") {print $1}' | head -n 1)
+
+ if [ $? -ne 0 ] || [ -z "$event_name" ] ; then
+ printf "FAIL: could not add event\n"
+ return 1
+ fi
+}
+
+trace_libc_inet_pton_backtrace() {
+
+ expected=`mktemp -u /tmp/expected.XXX`
+
+ echo "ping[][0-9 \.:]+$event_name: \([[:xdigit:]]+\)" > $expected
+ echo ".*inet_pton\+0x[[:xdigit:]]+[[:space:]]\($libc|inlined\)$" >> $expected
+ case "$(uname -m)" in
+ s390x)
+ eventattr='call-graph=dwarf,max-stack=8'
+ echo "((__GI_)?getaddrinfo|text_to_binary_address)\+0x[[:xdigit:]]+[[:space:]]\($libc|inlined\)$" >> $expected
+ echo "(gaih_inet|main)\+0x[[:xdigit:]]+[[:space:]]\(inlined|.*/bin/ping.*\)$" >> $expected
+ ;;
+ *)
+ eventattr='call-graph=dwarf,max-stack=8'
+ echo ".*(\+0x[[:xdigit:]]+|\[unknown\])[[:space:]]\(.*/bin/ping.*\)$" >> $expected
+ ;;
+ esac
+
+ perf_data=`mktemp -u /tmp/perf.data.XXX`
+ perf_script=`mktemp -u /tmp/perf.script.XXX`
+
+ # Check presence of libtraceevent support to run perf record
+ skip_no_probe_record_support "$event_name/$eventattr/"
+ if [ $? -eq 2 ]; then
+ echo "WARN: Skipping test trace_libc_inet_pton_backtrace. No libtraceevent support."
+ return 2
+ fi
+
+ perf record -e $event_name/$eventattr/ -o $perf_data ping -6 -c 1 ::1 > /dev/null 2>&1
+ # check if perf data file got created in above step.
+ if [ ! -e $perf_data ]; then
+ printf "FAIL: perf record failed to create \"%s\" \n" "$perf_data"
+ return 1
+ fi
+ perf script -i $perf_data | tac | grep -m1 ^ping -B9 | tac > $perf_script
+
+ exec 4<$expected
+ while read -r pattern <&4; do
+ echo "Pattern: $pattern"
+ [ -z "$pattern" ] && break
+
+ found=0
+
+ # Search lines in the perf script result
+ exec 3<$perf_script
+ while read line <&3; do
+ [ -z "$line" ] && break
+ echo " Matching: $line"
+ ! echo "$line" | grep -E -q "$pattern"
+ found=$?
+ [ $found -eq 1 ] && break
+ done
+
+ if [ $found -ne 1 ] ; then
+ printf "FAIL: Didn't find the expected backtrace entry \"%s\"\n" "$pattern"
+ return 1
+ fi
+ done
+
+ # If any statements are executed from this point onwards,
+ # the exit code of the last among these will be reflected
+ # in err below. If the exit code is 0, the test will pass
+ # even if the perf script output does not match.
+}
+
+delete_libc_inet_pton_event() {
+
+ if [ -n "$event_name" ] ; then
+ perf probe -q -d $event_name
+ fi
+}
+
+# Check for IPv6 interface existence
+ip a sh lo | grep -F -q inet6 || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+skip_if_no_perf_probe && \
+add_libc_inet_pton_event && \
+trace_libc_inet_pton_backtrace
+err=$?
+rm -f ${perf_data} ${perf_script} ${expected}
+delete_libc_inet_pton_event
+exit $err
diff --git a/tools/perf/tests/shell/record+script_probe_vfs_getname.sh b/tools/perf/tests/shell/record+script_probe_vfs_getname.sh
new file mode 100755
index 000000000..002f7037f
--- /dev/null
+++ b/tools/perf/tests/shell/record+script_probe_vfs_getname.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+# Use vfs_getname probe to get syscall args filenames (exclusive)
+
+# Uses the 'perf test shell' library to add probe:vfs_getname to the system
+# then use it with 'perf record' using 'touch' to write to a temp file, then
+# checks that that was captured by the vfs_getname probe in the generated
+# perf.data file, with the temp file name as the pathname argument.
+
+# SPDX-License-Identifier: GPL-2.0
+# Arnaldo Carvalho de Melo <acme@kernel.org>, 2017
+
+# shellcheck source=lib/probe.sh
+. "$(dirname "$0")/lib/probe.sh"
+
+skip_if_no_perf_probe || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+# shellcheck source=lib/probe_vfs_getname.sh
+. "$(dirname "$0")/lib/probe_vfs_getname.sh"
+
+record_open_file() {
+ echo "Recording open file:"
+ # Check presence of libtraceevent support to run perf record
+ skip_no_probe_record_support "probe:vfs_getname*"
+ if [ $? -eq 2 ]; then
+ echo "WARN: Skipping test record_open_file. No libtraceevent support"
+ return 2
+ fi
+ perf record -o ${perfdata} -e probe:vfs_getname\* touch $file
+}
+
+perf_script_filenames() {
+ echo "Looking at perf.data file for vfs_getname records for the file we touched:"
+ perf script -i ${perfdata} | \
+ grep -E " +touch +[0-9]+ +\[[0-9]+\] +[0-9]+\.[0-9]+: +probe:vfs_getname[_0-9]*: +\([[:xdigit:]]+\) +pathname=\"${file}\""
+}
+
+add_probe_vfs_getname
+err=$?
+
+if [ $err -eq 1 ] ; then
+ skip_if_no_debuginfo
+ err=$?
+fi
+
+if [ $err -ne 0 ] ; then
+ exit $err
+fi
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+file=$(mktemp /tmp/temporary_file.XXXXX)
+
+record_open_file && perf_script_filenames
+err=$?
+rm -f ${perfdata}
+rm -f ${file}
+cleanup_probe_vfs_getname
+exit $err
diff --git a/tools/perf/tests/shell/record+zstd_comp_decomp.sh b/tools/perf/tests/shell/record+zstd_comp_decomp.sh
new file mode 100755
index 000000000..f6b822238
--- /dev/null
+++ b/tools/perf/tests/shell/record+zstd_comp_decomp.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# Zstd perf.data compression/decompression
+
+# SPDX-License-Identifier: GPL-2.0
+
+trace_file=$(mktemp /tmp/perf.data.XXX)
+perf_tool=perf
+
+skip_if_no_z_record() {
+ $perf_tool record -h 2>&1 | grep -q '\-z, \-\-compression\-level'
+}
+
+collect_z_record() {
+ echo "Collecting compressed record file:"
+ [ "$(uname -m)" != s390x ] && gflag='-g'
+ $perf_tool record -o "$trace_file" $gflag -z -F 5000 -- \
+ dd count=500 if=/dev/urandom of=/dev/null
+}
+
+check_compressed_stats() {
+ echo "Checking compressed events stats:"
+ $perf_tool report -i "$trace_file" --header --stats | \
+ grep -E "(# compressed : Zstd,)|(COMPRESSED events:)"
+}
+
+check_compressed_output() {
+ $perf_tool inject -i "$trace_file" -o "$trace_file.decomp" &&
+ $perf_tool report -i "$trace_file" --stdio -F comm,dso,sym | head -n -3 > "$trace_file.comp.output" &&
+ $perf_tool report -i "$trace_file.decomp" --stdio -F comm,dso,sym | head -n -3 > "$trace_file.decomp.output" &&
+ diff "$trace_file.comp.output" "$trace_file.decomp.output"
+}
+
+skip_if_no_z_record || exit 2
+collect_z_record && check_compressed_stats && check_compressed_output
+err=$?
+rm -f "$trace_file*"
+exit $err
diff --git a/tools/perf/tests/shell/record+zstd_comp_decomp_multi_record.sh b/tools/perf/tests/shell/record+zstd_comp_decomp_multi_record.sh
new file mode 100755
index 000000000..c05ace821
--- /dev/null
+++ b/tools/perf/tests/shell/record+zstd_comp_decomp_multi_record.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+# Zstd perf.data compression/decompression of multi-record data
+# SPDX-License-Identifier: GPL-2.0
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+recout=$(mktemp /tmp/__perf_test.zstd.rec.XXXXX)
+injout=$(mktemp /tmp/__perf_test.zstd.inj.XXXXX)
+perf_tool=perf
+
+cleanup() {
+ rm -f "${perfdata}" "${perfdata}".old "${perfdata}".decomp "${recout}" "${injout}"
+}
+trap cleanup EXIT TERM INT
+
+skip_if_no_z_record() {
+ $perf_tool record -h 2>&1 | grep -q -- '-z, --compression-level'
+}
+
+collect_z_record() {
+ echo "Collecting compressed record file:"
+ [ "$(uname -m)" != s390x ] && gflag='-g'
+ $perf_tool record -o "${perfdata}" $gflag -z -F max -m 32M --per-thread -- \
+ $perf_tool test -w thloop 5 1 \
+ >/dev/null 2>"${recout}"
+}
+
+check_record() {
+ echo "Checking record did not fail to write data:"
+ if grep -q "failed to write perf data" "${recout}"; then
+ cat "${recout}"
+ return 1
+ fi
+}
+
+check_decompress() {
+ echo "Checking compressed file decompresses cleanly:"
+ if ! $perf_tool inject -i "${perfdata}" -o "${perfdata}".decomp 2>"${injout}"; then
+ cat "${injout}"
+ return 1
+ fi
+ if grep -Eqi "decompress|corrupt|failed to process type" "${injout}"; then
+ cat "${injout}"
+ return 1
+ fi
+}
+
+skip_if_no_z_record || exit 2
+collect_z_record
+check_record || exit 1
+
+# Need >1 record, else the multi-record path wasn't exercised.
+# Skip rather than pass/fail spuriously.
+nr=$($perf_tool report -i "${perfdata}" --stats 2>/dev/null |
+ awk '/COMPRESSED2 events:/ { print $3 }')
+if [ -z "${nr}" ] || [ "${nr}" -lt 2 ]; then
+ echo "less than two compressed records (${nr:-0}), skipping"
+ exit 2
+fi
+echo "Produced ${nr} compressed records"
+
+check_decompress
+err=$?
+exit $err
diff --git a/tools/perf/tests/shell/record.sh b/tools/perf/tests/shell/record.sh
new file mode 100755
index 000000000..625240809
--- /dev/null
+++ b/tools/perf/tests/shell/record.sh
@@ -0,0 +1,510 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf record tests
+
+set -e
+
+shelldir=$(dirname "$0")
+. "${shelldir}"/lib/perf_record.sh
+
+
+# shellcheck source=lib/waiting.sh
+. "${shelldir}"/lib/waiting.sh
+
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+testsym="test_loop"
+testsym2="brstack"
+
+skip_test_missing_symbol ${testsym}
+skip_test_missing_symbol ${testsym2}
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+script_output=$(mktemp /tmp/__perf_test.perf.data.XXXXX.script)
+testprog="perf test -w thloop"
+cpu_pmu_dir="/sys/bus/event_source/devices/cpu*"
+br_cntr_file="/caps/branch_counter_nr"
+br_cntr_output="branch stack counters"
+br_cntr_script_output="br_cntr: A"
+
+default_fd_limit=$(ulimit -Sn)
+# With option --threads=cpu the number of open file descriptors should be
+# equal to sum of: nmb_cpus * nmb_events (2+dummy),
+# nmb_threads for perf.data.n (equal to nmb_cpus) and
+# 2*nmb_cpus of pipes = 4*nmb_cpus (each pipe has 2 ends)
+# All together it needs 8*nmb_cpus file descriptors plus some are also used
+# outside of testing, thus raising the limit to 16*nmb_cpus
+min_fd_limit=$(($(getconf _NPROCESSORS_ONLN) * 16))
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${perfdata}".old
+ rm -f "${script_output}"
+ perf_record_cleanup
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+check_per_thread() {
+ perf report -i "${perfdata}" -q | grep -q "${testsym}"
+}
+
+test_per_thread() {
+ echo "Basic --per-thread mode test"
+ local ret=0
+ perf_record_with_retry "${perfdata}" "check_per_thread" "perf test -w thloop" \
+ --per-thread || ret=$?
+ if [ $ret -eq 2 ]; then
+ echo "Per-thread record [Skipped event not supported]"
+ return
+ elif [ $ret -eq 1 ]; then
+ echo "Per-thread record [Failed record or missing output]"
+ err=1
+ return
+ fi
+
+ # run the test program in background (for 30 seconds)
+ ${testprog} 30 &
+ TESTPID=$!
+
+ rm -f "${perfdata}"
+
+ wait_for_threads ${TESTPID} 2
+ perf record -p "${TESTPID}" --per-thread -o "${perfdata}" sleep 1 2> /dev/null
+ kill ${TESTPID}
+
+ if [ ! -e "${perfdata}" ]
+ then
+ echo "Per-thread record [Failed record -p]"
+ err=1
+ return
+ fi
+ if ! perf report -i "${perfdata}" -q | grep -q "${testsym}"
+ then
+ echo "Per-thread record [Failed -p missing output]"
+ err=1
+ return
+ fi
+
+ echo "Basic --per-thread mode test [Success]"
+}
+
+check_register_capture() {
+ perf script -F ip,sym,iregs -i "${perfdata}" 2>/dev/null | grep -q "DI:"
+}
+
+test_register_capture() {
+ echo "Register capture test"
+ if ! perf list pmu | grep -q 'br_inst_retired.near_call'
+ then
+ echo "Register capture test [Skipped missing event]"
+ return
+ fi
+ if ! perf record --intr-regs=\? 2>&1 | grep -q 'available registers: AX BX CX DX SI DI BP SP IP FLAGS CS SS R8 R9 R10 R11 R12 R13 R14 R15'
+ then
+ echo "Register capture test [Skipped missing registers]"
+ return
+ fi
+
+ local ret=0
+ perf_record_with_retry "${perfdata}" "check_register_capture" "perf test -w thloop" \
+ --intr-regs=di,r8,dx,cx -e br_inst_retired.near_call -c 1000 --per-thread || ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "Register capture test [Failed missing output]"
+ err=1
+ return
+ fi
+ echo "Register capture test [Success]"
+}
+
+check_system_wide() {
+ perf report -i "${perfdata}" -q | grep -q "${testsym}"
+}
+
+test_system_wide() {
+ echo "Basic --system-wide mode test"
+ local ret=0
+ perf_record_with_retry "${perfdata}" "check_system_wide" "perf test -w thloop" \
+ -aB --synth=no || ret=$?
+ if [ $ret -eq 2 ]; then
+ echo "System-wide record [Skipped not supported]"
+ return
+ elif [ $ret -eq 1 ]; then
+ echo "System-wide record [Failed missing output]"
+ err=1
+ return
+ fi
+
+ ret=0
+ perf_record_with_retry "${perfdata}" "check_system_wide" "perf test -w thloop" \
+ -aB --synth=no -e cpu-clock,cs --threads=cpu || ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "System-wide record [Failed record --threads option or missing output]"
+ err=1
+ return
+ fi
+ echo "Basic --system-wide mode test [Success]"
+}
+
+check_workload() {
+ perf report -i "${perfdata}" -q | grep -q "${testsym}"
+}
+
+test_workload() {
+ echo "Basic target workload test"
+ local ret=0
+ perf_record_with_retry "${perfdata}" "check_workload" "perf test -w thloop" || ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Workload record [Failed record or missing output]"
+ err=1
+ return
+ fi
+
+ ret=0
+ perf_record_with_retry "${perfdata}" "check_workload" "perf test -w thloop" \
+ -e cpu-clock,cs --threads=package || ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Workload record [Failed record --threads option or missing output]"
+ err=1
+ return
+ fi
+ echo "Basic target workload test [Success]"
+}
+
+check_branch_counter() {
+ perf report -i "${perfdata}" -D -q 2>/dev/null | grep -q "$br_cntr_output" && \
+ perf script -i "${perfdata}" -F +brstackinsn,+brcntr 2>/dev/null | \
+ grep -q "$br_cntr_script_output"
+}
+
+test_branch_counter() {
+ echo "Branch counter test"
+ # Check if the branch counter feature is supported
+ for dir in $cpu_pmu_dir
+ do
+ if [ ! -e "$dir$br_cntr_file" ]
+ then
+ echo "branch counter feature not supported on all core PMUs ($dir) [Skipped]"
+ return
+ fi
+ done
+ local ret=0
+ perf_record_with_retry "${perfdata}" "check_branch_counter" "perf test -w thloop" \
+ -e "{branches:p,instructions}" -j any,counter || ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Branch counter test [Failed record or missing output]"
+ err=1
+ return
+ fi
+ echo "Branch counter test [Success]"
+}
+
+check_cgroup() {
+ perf report -i "${perfdata}" -D 2>/dev/null | grep -q "CGROUP" && \
+ perf script -i "${perfdata}" -F cgroup 2>/dev/null | grep -q -v "unknown"
+}
+
+test_cgroup() {
+ echo "Cgroup sampling test"
+ local ret=0
+ perf_record_with_retry "${perfdata}" "check_cgroup" "perf test -w thloop" \
+ -aB --synth=cgroup --all-cgroups || ret=$?
+ if [ $ret -eq 2 ]; then
+ echo "Cgroup sampling [Skipped not supported]"
+ return
+ elif [ $ret -eq 1 ]; then
+ echo "Cgroup sampling [Failed missing output]"
+ err=1
+ return
+ fi
+ echo "Cgroup sampling test [Success]"
+}
+
+check_uid() {
+ perf report -i "${perfdata}" -q | grep -q "${testsym}"
+}
+
+test_uid() {
+ echo "Uid sampling test"
+ local ret=0
+ perf_record_with_retry "${perfdata}" "check_uid" "perf test -w thloop" \
+ -aB --synth=no --uid "$(id -u)" || ret=$?
+ if [ $ret -eq 2 ]; then
+ local logfile="${PERF_RECORD_LOGS[${#PERF_RECORD_LOGS[@]}-1]}"
+ if grep -q -E "libbpf.*EPERM|Access to performance monitoring" "$logfile" || \
+ grep -q -E "Permission denied|Failure to open any events" "$logfile"
+ then
+ echo "Uid sampling [Skipped permissions]"
+ return
+ else
+ echo "Uid sampling [Failed to record]"
+ err=1
+ return
+ fi
+ elif [ $ret -eq 1 ]; then
+ echo "Uid sampling [Failed missing output]"
+ err=1
+ return
+ fi
+ echo "Uid sampling test [Success]"
+}
+
+test_leader_sampling() {
+ echo "Basic leader sampling test"
+ events="{cycles,cycles}:Su"
+ [ "$(uname -m)" = "s390x" ] && {
+ [ ! -d /sys/devices/cpum_sf ] && {
+ echo "No CPUMF [Skipped record]"
+ return
+ }
+ events="{cpum_sf/SF_CYCLES_BASIC/,cycles}:Su"
+ perf record -o "${perfdata}" -e "$events" -- perf test -w brstack 2> /dev/null
+ # Perf grouping might be unsupported, depends on version.
+ [ "$?" -ne 0 ] && {
+ echo "Grouping not support [Skipped record]"
+ return
+ }
+ }
+ if ! perf record -o "${perfdata}" -e "$events" -- \
+ perf test -w brstack 2> /dev/null
+ then
+ echo "Leader sampling [Failed record]"
+ err=1
+ return
+ fi
+ perf script -i "${perfdata}" | grep brstack > $script_output
+ # Check if the two instruction counts are equal in each record.
+ # However, the throttling code doesn't consider event grouping. During throttling, only the
+ # leader is stopped, causing the slave's counts significantly higher. To temporarily solve this,
+ # let's set the tolerance rate to 80%.
+ # TODO: Revert the code for tolerance once the throttling mechanism is fixed.
+ index=0
+ valid_counts=0
+ invalid_counts=0
+ tolerance_rate=0.8
+ while IFS= read -r line
+ do
+ cycles=$(echo $line | awk '{for(i=1;i<=NF;i++) if($i=="cycles:") print $(i-1)}')
+ if [ $(($index%2)) -ne 0 ] && [ ${cycles}x != ${prev_cycles}x ]
+ then
+ invalid_counts=$(($invalid_counts+1))
+ else
+ valid_counts=$(($valid_counts+1))
+ fi
+ index=$(($index+1))
+ prev_cycles=$cycles
+ done < "${script_output}"
+ total_counts=$(bc <<< "$invalid_counts+$valid_counts")
+ if (( $(bc <<< "$total_counts <= 0") ))
+ then
+ echo "Leader sampling [No sample generated]"
+ err=1
+ return
+ fi
+ isok=$(bc <<< "scale=2; if (($invalid_counts/$total_counts) < (1-$tolerance_rate)) { 0 } else { 1 };")
+ if [ $isok -eq 1 ]
+ then
+ echo "Leader sampling [Failed inconsistent cycles count]"
+ err=1
+ else
+ echo "Basic leader sampling test [Success]"
+ fi
+}
+
+test_topdown_leader_sampling() {
+ echo "Topdown leader sampling test"
+ if ! perf stat -e "{slots,topdown-retiring}" true 2> /dev/null
+ then
+ echo "Topdown leader sampling [Skipped event parsing failed]"
+ return
+ fi
+ if ! perf record -o "${perfdata}" -e "{instructions,slots,topdown-retiring}:S" true 2> /dev/null
+ then
+ echo "Topdown leader sampling [Failed topdown events not reordered correctly]"
+ err=1
+ return
+ fi
+ echo "Topdown leader sampling test [Success]"
+}
+
+test_precise_max() {
+ local -i skipped=0
+
+ echo "precise_max attribute test"
+ # Just to make sure event cycles is supported for sampling
+ if perf record -o "${perfdata}" -e "cycles" true 2> /dev/null
+ then
+ if ! perf record -o "${perfdata}" -e "cycles:P" true 2> /dev/null
+ then
+ echo "precise_max attribute [Failed cycles:P event]"
+ err=1
+ return
+ fi
+ else
+ echo "precise_max attribute [Skipped no cycles:P event]"
+ ((skipped+=1))
+ fi
+ # On s390 event instructions is not supported for perf record
+ if perf record -o "${perfdata}" -e "instructions" true 2> /dev/null
+ then
+ # On AMD, cycles and instructions events are treated differently
+ if ! perf record -o "${perfdata}" -e "instructions:P" true 2> /dev/null
+ then
+ echo "precise_max attribute [Failed instructions:P event]"
+ err=1
+ return
+ fi
+ else
+ echo "precise_max attribute [Skipped no instructions:P event]"
+ ((skipped+=1))
+ fi
+ if [ $skipped -eq 2 ]
+ then
+ echo "precise_max attribute [Skipped no hardware events]"
+ else
+ echo "precise_max attribute test [Success]"
+ fi
+}
+
+test_callgraph() {
+ echo "Callgraph test"
+
+ case $(uname -m)
+ in s390x)
+ cmd_flags="--call-graph dwarf -e cpu-clock";;
+ *)
+ cmd_flags="-g";;
+ esac
+
+ if ! perf record -o "${perfdata}" $cmd_flags perf test -w brstack
+ then
+ echo "Callgraph test [Failed missing output]"
+ err=1
+ return
+ fi
+
+ if ! perf report -i "${perfdata}" 2>&1 | grep "${testsym2}"
+ then
+ echo "Callgraph test [Failed missing symbol]"
+ err=1
+ return
+ fi
+
+ echo "Callgraph test [Success]"
+}
+
+test_acr_sampling() {
+ events="{instructions/period=40000,acr_mask=0x2/u,cycles/period=20000,acr_mask=0x3/u}"
+ pebs_events="{instructions/period=40000,acr_mask=0x2/pu,cycles/period=20000,acr_mask=0x3/u}"
+ echo "Auto counter reload (ACR) sampling test"
+ if ! perf record -o "${perfdata}" -e "${events}" ${testprog} 2> /dev/null
+ then
+ echo "Auto counter reload sampling [Skipped not supported]"
+ return
+ fi
+ if ! perf script -i "${perfdata}" -F event | grep -q "instructions"
+ then
+ echo "Auto counter reload sampling [Failed missing instructions event]"
+ err=1
+ return
+ fi
+ if perf script -i "${perfdata}" -F event | grep -q "cycles"
+ then
+ echo "Auto counter reload sampling [Failed cycles event shouldn't be sampled]"
+ err=1
+ return
+ fi
+ if ! perf record -o "${perfdata}" -e "${pebs_events}" ${testprog} 2> /dev/null
+ then
+ echo "Auto counter reload PEBS sampling [Skipped not supported]"
+ echo "Auto counter reload sampling [Success]"
+ return
+ fi
+ if ! perf script -i "${perfdata}" -F event | grep -q "instructions"
+ then
+ echo "Auto counter reload PEBS sampling [Failed missing instructions event]"
+ err=1
+ return
+ fi
+ if perf script -i "${perfdata}" -F event | grep -q "cycles"
+ then
+ echo "Auto counter reload PEBS sampling [Failed cycles event shouldn't be sampled]"
+ err=1
+ return
+ fi
+ echo "Auto counter reload sampling [Success]"
+}
+
+test_ratio_to_prev() {
+ echo "ratio-to-prev test"
+ if ! perf record -o /dev/null -e "{instructions, cycles/period=100000,ratio-to-prev=0.5/}" \
+ true 2> /dev/null
+ then
+ echo "ratio-to-prev [Skipped not supported]"
+ return
+ fi
+ if ! perf record -o /dev/null -e "instructions, cycles/period=100000,ratio-to-prev=0.5/" \
+ true |& grep -q 'Invalid use of ratio-to-prev term without preceding element in group'
+ then
+ echo "ratio-to-prev test [Failed elements must be in same group]"
+ err=1
+ return
+ fi
+ if ! perf record -o /dev/null -e "{instructions,dummy,cycles/period=100000,ratio-to-prev=0.5/}" \
+ true |& grep -q 'must have same PMU'
+ then
+ echo "ratio-to-prev test [Failed elements must have same PMU]"
+ err=1
+ return
+ fi
+ if ! perf record -o /dev/null -e "{instructions,cycles/ratio-to-prev=0.5/}" \
+ true |& grep -q 'Event period term or count (-c) must be set when using ratio-to-prev term.'
+ then
+ echo "ratio-to-prev test [Failed period must be set]"
+ err=1
+ return
+ fi
+ if ! perf record -o /dev/null -e "{cycles/ratio-to-prev=0.5/}" \
+ true |& grep -q 'Invalid use of ratio-to-prev term without preceding element in group'
+ then
+ echo "ratio-to-prev test [Failed need 2+ events]"
+ err=1
+ return
+ fi
+ echo "Basic ratio-to-prev record test [Success]"
+}
+
+# raise the limit of file descriptors to minimum
+if [[ $default_fd_limit -lt $min_fd_limit ]]; then
+ ulimit -Sn $min_fd_limit
+fi
+
+test_per_thread
+test_register_capture
+test_system_wide
+test_workload
+test_branch_counter
+test_cgroup
+test_uid
+test_leader_sampling
+test_topdown_leader_sampling
+test_precise_max
+test_callgraph
+test_acr_sampling
+test_ratio_to_prev
+
+# restore the default value
+ulimit -Sn $default_fd_limit
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/record_bpf_filter.sh b/tools/perf/tests/shell/record_bpf_filter.sh
new file mode 100755
index 000000000..383574cb3
--- /dev/null
+++ b/tools/perf/tests/shell/record_bpf_filter.sh
@@ -0,0 +1,202 @@
+#!/bin/bash
+# perf record sample filtering (by BPF) tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${perfdata}".old
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_bpf_filter_priv() {
+ echo "Checking BPF-filter privilege"
+
+ if ! perf record -e task-clock --filter 'period > 1' \
+ -o /dev/null --quiet true 2>&1
+ then
+ if [ "$(id -u)" != 0 ]
+ then
+ echo "try 'sudo perf record --setup-filter pin' first."
+ echo "bpf-filter test [Skipped permission]"
+ err=2
+ return
+ fi
+ echo "bpf-filter test [Skipped missing BPF support]"
+ err=2
+ return
+ fi
+}
+
+test_bpf_filter_basic() {
+ echo "Basic bpf-filter test"
+
+ if ! perf record -e task-clock -c 10000 --filter 'ip < 0xffffffff00000000' \
+ -o "${perfdata}" true 2> /dev/null
+ then
+ echo "Basic bpf-filter test [Failed record]"
+ err=1
+ return
+ fi
+ if perf script -i "${perfdata}" -F ip | grep 'ffffffff[0-9a-f]*'
+ then
+ if uname -r | grep -q ^6.2
+ then
+ echo "Basic bpf-filter test [Skipped unsupported kernel]"
+ err=2
+ return
+ fi
+ echo "Basic bpf-filter test [Failed invalid output]"
+ err=1
+ return
+ fi
+ echo "Basic bpf-filter test [Success]"
+}
+
+test_bpf_filter_fail() {
+ echo "Failing bpf-filter test"
+
+ # 'cpu' requires PERF_SAMPLE_CPU flag
+ if ! perf record -e task-clock --filter 'cpu > 0' \
+ -o /dev/null true 2>&1 | grep -q PERF_SAMPLE_CPU
+ then
+ echo "Failing bpf-filter test [Failed forbidden CPU]"
+ err=1
+ return
+ fi
+
+ if ! perf record --sample-cpu -e task-clock --filter 'cpu > 0' \
+ -o /dev/null true 2>/dev/null
+ then
+ echo "Failing bpf-filter test [Failed should succeed]"
+ err=1
+ return
+ fi
+
+ echo "Failing bpf-filter test [Success]"
+}
+
+test_bpf_filter_group() {
+ echo "Group bpf-filter test"
+
+ if ! perf record -e task-clock --filter 'period > 1000, ip > 0' \
+ -o /dev/null true 2>/dev/null
+ then
+ echo "Group bpf-filter test [Failed should succeed]"
+ err=1
+ return
+ fi
+
+ if ! perf record -e task-clock --filter 'period > 1000 , cpu > 0 || ip > 0' \
+ -o /dev/null true 2>&1 | grep -q PERF_SAMPLE_CPU
+ then
+ echo "Group bpf-filter test [Failed forbidden CPU]"
+ err=1
+ return
+ fi
+
+ if ! perf record -e task-clock --filter 'period > 0 || code_pgsz > 4096' \
+ -o /dev/null true 2>&1 | grep -q PERF_SAMPLE_CODE_PAGE_SIZE
+ then
+ echo "Group bpf-filter test [Failed forbidden CODE_PAGE_SIZE]"
+ err=1
+ return
+ fi
+
+ echo "Group bpf-filter test [Success]"
+}
+
+test_bpf_filter_multi() {
+ echo "Multiple bpf-filter test"
+
+ if ! perf record -e task-clock --filter 'period > 100000' \
+ -e page-faults --filter 'ip < 0xffffffff00000000' \
+ -o "${perfdata}" true 2> /dev/null
+ then
+ echo "Multiple bpf-filter test [Failed record]"
+ err=1
+ return
+ fi
+
+ if ! perf script -i "${perfdata}" -F period,event | grep task-clock | \
+ awk '{ if (int($1) <= 100000) { print $0; exit(1); } }'
+ then
+ echo "Multiple bpf-filter test [Failed task-clock period]"
+ err=1
+ return
+ fi
+
+ if perf script -i "${perfdata}" -F event,ip | grep page-fault | \
+ grep 'ffffffff[0-9a-f]*'
+ then
+ echo "Multiple bpf-filter test [Failed page-faults ip]"
+ err=1
+ return
+ fi
+
+ echo "Multiple bpf-filter test [Success]"
+}
+
+test_bpf_filter_cgroup() {
+ echo "Cgroup bpf-filter test"
+
+ if ! perf record -e task-clock --filter 'cgroup == /' \
+ -a --all-cgroups --synth=cgroup -o "${perfdata}" true 2> /dev/null
+ then
+ echo "Cgroup bpf-filter test [Skipped cgroup not supported]"
+ return
+ fi
+
+ # 'cgroup' requires PERF_SAMPLE_CGROUP flag
+ if ! perf record -e task-clock --filter 'cgroup == /' \
+ -o /dev/null true 2>&1 | grep -q PERF_SAMPLE_CGROUP
+ then
+ echo "Cgroup bpf-filter test [Failed CGROUP requires --all-cgroups]"
+ err=1
+ return
+ fi
+
+ if ! perf report -i "${perfdata}" -s cgroup -q | grep -q -F '100.00%'
+ then
+ echo "Cgroup bpf-filter test [Failed root cgroup does not have 100%]"
+ err=1
+ return
+ fi
+
+ echo "Cgroup bpf-filter test [Success]"
+}
+
+test_bpf_filter_priv
+
+if [ $err = 0 ]; then
+ test_bpf_filter_basic
+fi
+
+if [ $err = 0 ]; then
+ test_bpf_filter_fail
+fi
+
+if [ $err = 0 ]; then
+ test_bpf_filter_group
+fi
+
+if [ $err = 0 ]; then
+ test_bpf_filter_multi
+fi
+
+if [ $err = 0 ]; then
+ test_bpf_filter_cgroup
+fi
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/record_lbr.sh b/tools/perf/tests/shell/record_lbr.sh
new file mode 100755
index 000000000..8d51afeb4
--- /dev/null
+++ b/tools/perf/tests/shell/record_lbr.sh
@@ -0,0 +1,194 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf record LBR tests
+
+set -e
+
+shelldir=$(dirname "$0")
+. "${shelldir}"/lib/perf_record.sh
+
+ParanoidAndNotRoot() {
+ [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
+}
+
+if [ ! -f /sys/bus/event_source/devices/cpu/caps/branches ] &&
+ [ ! -f /sys/bus/event_source/devices/cpu_core/caps/branches ]
+then
+ echo "Skip: only x86 CPUs support LBR"
+ exit 2
+fi
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+
+cleanup() {
+ rm -rf "${perfdata}"
+ rm -rf "${perfdata}".old
+ rm -rf "${perfdata}".txt
+ perf_record_cleanup
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+
+check_lbr_callgraph() {
+ perf report --stitch-lbr -i "${perfdata}" > "${perfdata}".txt 2>&1
+}
+
+lbr_callgraph_test() {
+ test="LBR callgraph"
+
+ echo "$test"
+ set +e
+ perf_record_with_retry "${perfdata}" "check_lbr_callgraph" "perf test -w thloop" \
+ -e cycles --call-graph lbr
+ local ret=$?
+ set -e
+
+ if [ $ret -eq 2 ]; then
+ echo "$test [Failed support missing]"
+ if [ $err -eq 0 ]
+ then
+ err=2
+ fi
+ return
+ elif [ $ret -eq 1 ]; then
+ cat "${perfdata}".txt
+ echo "$test [Failed in perf report]"
+ err=1
+ return
+ fi
+
+ echo "$test [Success]"
+}
+
+check_lbr_samples() {
+ local out
+ out=$(perf report -D -i "${perfdata}" 2> /dev/null | grep -A1 'PERF_RECORD_SAMPLE')
+ [ "$(echo "$out" | grep -c 'PERF_RECORD_SAMPLE' || true)" -gt 0 ]
+}
+
+lbr_test() {
+ local branch_flags=$1
+ local test="LBR $2 test"
+ local threshold=$3
+ local out
+ local sam_nr
+ local bs_nr
+ local zero_nr
+ local r
+
+ echo "$test"
+ set +e
+ perf_record_with_retry "${perfdata}" "check_lbr_samples" "perf test -w thloop" \
+ -e cycles $branch_flags
+ local ret=$?
+ set -e
+
+ if [ $ret -eq 2 ]; then
+ echo "$test [Failed support missing]"
+ if [ $err -eq 0 ]
+ then
+ err=2
+ fi
+ return
+ elif [ $ret -eq 1 ]; then
+ echo "$test [Failed no samples captured]"
+ err=1
+ return
+ fi
+
+ out=$(perf report -D -i "${perfdata}" 2> /dev/null | grep -A1 'PERF_RECORD_SAMPLE')
+ sam_nr=$(echo "$out" | grep -c 'PERF_RECORD_SAMPLE' || true)
+ echo "$test: $sam_nr samples"
+
+ bs_nr=$(echo "$out" | grep -c 'branch stack: nr:' || true)
+ if [ $sam_nr -ne $bs_nr ]
+ then
+ echo "$test [Failed samples missing branch stacks]"
+ err=1
+ return
+ fi
+
+ zero_nr=$(echo "$out" | grep -A3 'branch stack: nr:0' | grep thread | grep -cv swapper || true)
+ r=$(($zero_nr * 100 / $bs_nr))
+ if [ $r -gt $threshold ]; then
+ echo "$test [Failed empty br stack ratio exceed $threshold%: $r%]"
+ err=1
+ return
+ fi
+
+ echo "$test [Success]"
+}
+
+parallel_lbr_test() {
+ err=0
+ perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+ lbr_test "$1" "$2" "$3"
+ cleanup
+ exit $err
+}
+
+lbr_callgraph_test
+
+# Sequential
+lbr_test "-b" "any branch" 2
+lbr_test "-j any_call" "any call" 2
+lbr_test "-j any_ret" "any ret" 2
+lbr_test "-j ind_call" "any indirect call" 2
+lbr_test "-j ind_jmp" "any indirect jump" 100
+lbr_test "-j call" "direct calls" 2
+lbr_test "-j ind_call,u" "any indirect user call" 100
+if ! ParanoidAndNotRoot 1
+then
+ lbr_test "-a -b" "system wide any branch" 2
+ lbr_test "-a -j any_call" "system wide any call" 2
+fi
+
+# Parallel
+parallel_lbr_test "-b" "parallel any branch" 100 &
+pid1=$!
+parallel_lbr_test "-j any_call" "parallel any call" 100 &
+pid2=$!
+parallel_lbr_test "-j any_ret" "parallel any ret" 100 &
+pid3=$!
+parallel_lbr_test "-j ind_call" "parallel any indirect call" 100 &
+pid4=$!
+parallel_lbr_test "-j ind_jmp" "parallel any indirect jump" 100 &
+pid5=$!
+parallel_lbr_test "-j call" "parallel direct calls" 100 &
+pid6=$!
+parallel_lbr_test "-j ind_call,u" "parallel any indirect user call" 100 &
+pid7=$!
+if ParanoidAndNotRoot 1
+then
+ pid8=
+ pid9=
+else
+ parallel_lbr_test "-a -b" "parallel system wide any branch" 100 &
+ pid8=$!
+ parallel_lbr_test "-a -j any_call" "parallel system wide any call" 100 &
+ pid9=$!
+fi
+
+for pid in $pid1 $pid2 $pid3 $pid4 $pid5 $pid6 $pid7 $pid8 $pid9
+do
+ set +e
+ wait $pid
+ child_err=$?
+ set -e
+ if ([ $err -eq 2 ] && [ $child_err -eq 1 ]) || [ $err -eq 0 ]
+ then
+ err=$child_err
+ fi
+done
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/record_offcpu.sh b/tools/perf/tests/shell/record_offcpu.sh
new file mode 100755
index 000000000..ee095df47
--- /dev/null
+++ b/tools/perf/tests/shell/record_offcpu.sh
@@ -0,0 +1,174 @@
+#!/bin/bash
+# perf record offcpu profiling tests (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+
+ts=$(printf "%u" $((~0 << 32))) # OFF_CPU_TIMESTAMP
+dummy_timestamp=${ts%???} # remove the last 3 digits to match perf script
+
+cleanup() {
+ rm -f ${perfdata}
+ rm -f ${perfdata}.old
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_above_thresh="Threshold test (above threshold)"
+test_below_thresh="Threshold test (below threshold)"
+
+test_offcpu_priv() {
+ echo "Checking off-cpu privilege"
+
+ if [ "$(id -u)" != 0 ]
+ then
+ echo "off-cpu test [Skipped permission]"
+ err=2
+ return
+ fi
+ if perf version --build-options 2>&1 | grep HAVE_BPF_SKEL | grep -q OFF
+ then
+ echo "off-cpu test [Skipped missing BPF support]"
+ err=2
+ return
+ fi
+}
+
+test_offcpu_basic() {
+ echo "Basic off-cpu test"
+
+ if ! perf record --off-cpu -e dummy -o ${perfdata} sleep 0.3 2> /dev/null
+ then
+ echo "Basic off-cpu test [Failed record]"
+ err=1
+ return
+ fi
+ if ! perf evlist -i ${perfdata} | grep -q "offcpu-time"
+ then
+ echo "Basic off-cpu test [Failed no event]"
+ err=1
+ return
+ fi
+ if ! perf report -i ${perfdata} -q --percent-limit=70 | grep -E -q sleep
+ then
+ echo "Basic off-cpu test [Failed missing output]"
+ err=1
+ return
+ fi
+ echo "Basic off-cpu test [Success]"
+}
+
+test_offcpu_child() {
+ echo "Child task off-cpu test"
+
+ # perf bench sched messaging creates 400 processes
+ if ! perf record --off-cpu -e dummy -o ${perfdata} -- \
+ perf bench sched messaging -g 10 > /dev/null 2>&1
+ then
+ echo "Child task off-cpu test [Failed record]"
+ err=1
+ return
+ fi
+ if ! perf evlist -i ${perfdata} | grep -q "offcpu-time"
+ then
+ echo "Child task off-cpu test [Failed no event]"
+ err=1
+ return
+ fi
+ # each process waits at least for poll, so it should be more than 400 events
+ if ! perf report -i ${perfdata} -s comm -q -n -t ';' --percent-limit=90 | \
+ awk -F ";" '{ if (NF > 3 && int($3) < 400) exit 1; }'
+ then
+ echo "Child task off-cpu test [Failed invalid output]"
+ err=1
+ return
+ fi
+ echo "Child task off-cpu test [Success]"
+}
+
+# task blocks longer than the --off-cpu-thresh, perf should collect a direct sample
+test_offcpu_above_thresh() {
+ echo "${test_above_thresh}"
+
+ # collect direct off-cpu samples for tasks blocked for more than 50ms
+ if ! perf record -e dummy --off-cpu --off-cpu-thresh 50 -o ${perfdata} -- sleep 0.1 2> /dev/null
+ then
+ echo "${test_above_thresh} [Failed record]"
+ err=1
+ return
+ fi
+ # direct sample's timestamp should be lower than the dummy_timestamp of the at-the-end sample
+ # check if a direct sample exists
+ if ! perf script --time "0, ${dummy_timestamp}" -i ${perfdata} -F event | grep -q "offcpu-time"
+ then
+ echo "${test_above_thresh} [Failed missing direct samples]"
+ err=1
+ return
+ fi
+ # there should only be one direct sample, and its period should be higher than off-cpu-thresh
+ if ! perf script --time "0, ${dummy_timestamp}" -i ${perfdata} -F period | \
+ awk '{ if (int($1) > 50000000) exit 0; else exit 1; }'
+ then
+ echo "${test_above_thresh} [Failed off-cpu time too short]"
+ err=1
+ return
+ fi
+ echo "${test_above_thresh} [Success]"
+}
+
+# task blocks shorter than the --off-cpu-thresh, perf should collect an at-the-end sample
+test_offcpu_below_thresh() {
+ echo "${test_below_thresh}"
+
+ # collect direct off-cpu samples for tasks blocked for more than 500ms
+ if ! perf record -e dummy --off-cpu --off-cpu-thresh 500 -o ${perfdata} -- sleep 0.1 2> /dev/null
+ then
+ echo "${test_below_thresh} [Failed record]"
+ err=1
+ return
+ fi
+ # see if there's an at-the-end sample
+ if ! perf script --time "${dummy_timestamp}," -i ${perfdata} -F event | grep -q 'offcpu-time'
+ then
+ echo "${test_below_thresh} [Failed at-the-end samples cannot be found]"
+ err=1
+ return
+ fi
+ # plus there shouldn't be any direct samples
+ if perf script --time "0, ${dummy_timestamp}" -i ${perfdata} -F event | grep -q 'offcpu-time'
+ then
+ echo "${test_below_thresh} [Failed direct samples are found when they shouldn't be]"
+ err=1
+ return
+ fi
+ echo "${test_below_thresh} [Success]"
+}
+
+test_offcpu_priv
+
+if [ $err = 0 ]; then
+ test_offcpu_basic
+fi
+
+if [ $err = 0 ]; then
+ test_offcpu_child
+fi
+
+if [ $err = 0 ]; then
+ test_offcpu_above_thresh
+fi
+
+if [ $err = 0 ]; then
+ test_offcpu_below_thresh
+fi
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/record_sideband.sh b/tools/perf/tests/shell/record_sideband.sh
new file mode 100755
index 000000000..218255187
--- /dev/null
+++ b/tools/perf/tests/shell/record_sideband.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+# perf record sideband tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+
+cleanup()
+{
+ rm -rf ${perfdata}
+ trap - EXIT TERM INT
+}
+
+trap_cleanup()
+{
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+can_cpu_wide()
+{
+ if ! perf record -o ${perfdata} -BN --no-bpf-event -C $1 true > /dev/null 2>&1
+ then
+ echo "record sideband test [Skipped cannot record cpu$1]"
+ err=2
+ fi
+
+ rm -f ${perfdata}
+ return $err
+}
+
+test_system_wide_tracking()
+{
+ # Need CPU 0 and CPU 1
+ can_cpu_wide 0 || return 0
+ can_cpu_wide 1 || return 0
+
+ # Record on CPU 0 a task running on CPU 1
+ perf record -BN --no-bpf-event -o ${perfdata} -C 0 -- taskset --cpu-list 1 true
+
+ # Should get MMAP events from CPU 1
+ mmap_cnt=`perf script -i ${perfdata} --show-mmap-events -C 1 2>/dev/null | grep MMAP | wc -l`
+
+ if [ ${mmap_cnt} -gt 0 ] ; then
+ return 0
+ fi
+
+ echo "Failed to record MMAP events on CPU 1 when tracing CPU 0"
+ return 1
+}
+
+test_system_wide_tracking
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/record_weak_term.sh b/tools/perf/tests/shell/record_weak_term.sh
new file mode 100755
index 000000000..811b00ffb
--- /dev/null
+++ b/tools/perf/tests/shell/record_weak_term.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# record weak terms
+# SPDX-License-Identifier: GPL-2.0
+# Test that command line options override weak terms from sysfs or inbuilt json.
+set -e
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/setup_python.sh
+. "${shelldir}"/lib/setup_python.sh
+
+# Find the first event with a specified period, such as
+# "cpu_core/event=0x24,period=200003,umask=0xff/"
+event=$(perf list --json | $PYTHON -c '
+import json, sys
+for e in json.load(sys.stdin):
+ if "EventName" not in e or "/modifier" in e["EventName"]:
+ continue
+ if "Encoding" in e and "period=" in e["Encoding"]:
+ print(e["EventName"])
+ break
+')
+if [[ "$event" = "" ]]
+then
+ echo "Skip: No sysfs/json events with inbuilt period."
+ exit 2
+fi
+
+echo "Testing that for $event the period is overridden with 1000"
+perf list --detail "$event"
+if ! perf record -c 1000 -vv -e "$event" -o /dev/null true 2>&1 | \
+ grep -q -F '{ sample_period, sample_freq } 1000'
+then
+ echo "Fail: Unexpected verbose output and sample period"
+ exit 1
+fi
+echo "Success"
+exit 0
diff --git a/tools/perf/tests/shell/sched.sh b/tools/perf/tests/shell/sched.sh
new file mode 100755
index 000000000..113df962a
--- /dev/null
+++ b/tools/perf/tests/shell/sched.sh
@@ -0,0 +1,145 @@
+#!/bin/bash
+# perf sched tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+if [ "$(id -u)" != 0 ]; then
+ echo "[Skip] No root permission"
+ exit 2
+fi
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test_sched.perf.data.XXXXX)
+PID1=0
+PID2=0
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${perfdata}".old
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+start_noploops() {
+ # Start two noploop workloads on CPU0 to trigger scheduling.
+ perf test -w noploop 10 &
+ PID1=$!
+ taskset -pc 0 $PID1
+ perf test -w noploop 10 &
+ PID2=$!
+ taskset -pc 0 $PID2
+
+ if ! grep -q 'Cpus_allowed_list:\s*0$' "/proc/$PID1/status"
+ then
+ echo "Sched [Error taskset did not work for the 1st noploop ($PID1)]"
+ grep Cpus_allowed /proc/$PID1/status
+ err=1
+ fi
+
+ if ! grep -q 'Cpus_allowed_list:\s*0$' "/proc/$PID2/status"
+ then
+ echo "Sched [Error taskset did not work for the 2nd noploop ($PID2)]"
+ grep Cpus_allowed /proc/$PID2/status
+ err=1
+ fi
+}
+
+cleanup_noploops() {
+ kill "$PID1" "$PID2" || true
+}
+
+test_sched_record() {
+ echo "Sched record"
+
+ start_noploops
+
+ perf sched record --no-inherit -o "${perfdata}" sleep 1
+
+ cleanup_noploops
+}
+
+test_sched_latency() {
+ echo "Sched latency"
+
+ if ! perf sched latency -i "${perfdata}" | grep -q perf-noploop
+ then
+ echo "Sched latency [Failed missing output]"
+ err=1
+ fi
+}
+
+test_sched_latency_histogram() {
+ echo "Sched latency histogram"
+
+ if ! perf sched latency -H -i "${perfdata}" | grep -q "Latency Distribution Histogram"
+ then
+ echo "Sched latency histogram [Failed missing log histogram]"
+ err=1
+ fi
+
+ if ! perf sched latency --histogram --hist-mode linear -i "${perfdata}" | grep -q "Latency Distribution Histogram"
+ then
+ echo "Sched latency histogram [Failed missing linear histogram]"
+ err=1
+ fi
+}
+
+test_sched_latency_time() {
+ echo "Sched latency time filter"
+
+ if ! perf sched latency --time 0, -i "${perfdata}" | grep -q perf-noploop
+ then
+ echo "Sched latency time filter [Failed missing output]"
+ err=1
+ fi
+}
+
+test_sched_script() {
+ echo "Sched script"
+
+ if ! perf sched script -i "${perfdata}" | grep -q perf-noploop
+ then
+ echo "Sched script [Failed missing output]"
+ err=1
+ fi
+}
+
+test_sched_map() {
+ echo "Sched map"
+
+ if ! perf sched map -i "${perfdata}" | grep -q perf-noploop
+ then
+ echo "Sched map [Failed missing output]"
+ err=1
+ fi
+}
+
+test_sched_timehist() {
+ echo "Sched timehist"
+
+ if ! perf sched timehist -i "${perfdata}" | grep -q perf-noploop
+ then
+ echo "Sched timehist [Failed missing output]"
+ err=1
+ fi
+}
+
+test_sched_record
+test_sched_latency
+test_sched_latency_histogram
+test_sched_latency_time
+test_sched_script
+test_sched_map
+test_sched_timehist
+
+cleanup
+exit $err
+
diff --git a/tools/perf/tests/shell/script.sh b/tools/perf/tests/shell/script.sh
new file mode 100755
index 000000000..7007f1cdf
--- /dev/null
+++ b/tools/perf/tests/shell/script.sh
@@ -0,0 +1,99 @@
+#!/bin/bash
+# perf script tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+temp_dir=$(mktemp -d /tmp/perf-test-script.XXXXXXXXXX)
+
+perfdatafile="${temp_dir}/perf.data"
+db_test="${temp_dir}/db_test.py"
+
+err=0
+
+cleanup()
+{
+ trap - EXIT TERM INT
+ sane=$(echo "${temp_dir}" | cut -b 1-21)
+ if [ "${sane}" = "/tmp/perf-test-script" ] ; then
+ echo "--- Cleaning up ---"
+ rm -rf "${temp_dir:?}/"*
+ rmdir "${temp_dir}"
+ fi
+}
+
+trap_cleanup()
+{
+ cleanup
+ exit 1
+}
+
+trap trap_cleanup EXIT TERM INT
+
+
+test_db()
+{
+ echo "DB test"
+
+ # Check if python script is supported
+ if perf version --build-options | grep python | grep -q OFF ; then
+ echo "SKIP: python scripting is not supported"
+ err=2
+ return
+ fi
+
+ cat << "_end_of_file_" > "${db_test}"
+perf_db_export_mode = True
+perf_db_export_calls = False
+perf_db_export_callchains = True
+
+def sample_table(*args):
+ print(f'sample_table({args})')
+
+def call_path_table(*args):
+ print(f'call_path_table({args}')
+_end_of_file_
+ case $(uname -m)
+ in s390x)
+ cmd_flags="--call-graph dwarf -e cpu-clock";;
+ *)
+ cmd_flags="-g";;
+ esac
+
+ perf record $cmd_flags -o "${perfdatafile}" true
+ # Disable lsan to avoid warnings about python memory leaks.
+ export ASAN_OPTIONS=detect_leaks=0
+ perf script -i "${perfdatafile}" -s "${db_test}"
+ export ASAN_OPTIONS=
+ echo "DB test [Success]"
+}
+
+test_parallel_perf()
+{
+ echo "parallel-perf test"
+ if ! python3 --version >/dev/null 2>&1 ; then
+ echo "SKIP: no python3"
+ err=2
+ return
+ fi
+ pp=$(dirname "$0")/../../scripts/python/parallel-perf.py
+ if [ ! -f "${pp}" ] ; then
+ echo "SKIP: parallel-perf.py script not found "
+ err=2
+ return
+ fi
+ perf_data="${temp_dir}/pp-perf.data"
+ output1_dir="${temp_dir}/output1"
+ output2_dir="${temp_dir}/output2"
+ perf record -o "${perf_data}" --sample-cpu uname
+ python3 "${pp}" -o "${output1_dir}" --jobs 4 --verbose -- perf script -i "${perf_data}"
+ python3 "${pp}" -o "${output2_dir}" --jobs 4 --verbose --per-cpu -- perf script -i "${perf_data}"
+ echo "parallel-perf test [Success]"
+}
+
+test_db
+test_parallel_perf
+
+cleanup
+
+exit $err
diff --git a/tools/perf/tests/shell/script_dlfilter.sh b/tools/perf/tests/shell/script_dlfilter.sh
new file mode 100755
index 000000000..aaed92bb7
--- /dev/null
+++ b/tools/perf/tests/shell/script_dlfilter.sh
@@ -0,0 +1,107 @@
+#!/bin/bash
+# perf script --dlfilter tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/setup_python.sh
+. "${shelldir}"/lib/setup_python.sh
+
+# skip if there's no compiler
+if ! [ -x "$(command -v cc)" ]; then
+ echo "failed: no compiler, install gcc"
+ exit 2
+fi
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+dlfilter_c=$(mktemp /tmp/__perf_test.dlfilter.test.c.XXXXX)
+dlfilter_so=$(mktemp /tmp/__perf_test.dlfilter.so.XXXXX)
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${dlfilter_c}"
+ rm -f "${dlfilter_so}"
+ rm -f "${dlfilter_so}.o"
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+cat <<EOF > "${dlfilter_c}"
+#include <perf/perf_dlfilter.h>
+#include <string.h>
+#include <stdio.h>
+
+struct perf_dlfilter_fns perf_dlfilter_fns;
+
+int filter_event(void *data, const struct perf_dlfilter_sample *sample, void *ctx)
+{
+ const struct perf_dlfilter_al *al;
+
+ if (!sample->ip)
+ return 0;
+
+ al = perf_dlfilter_fns.resolve_ip(ctx);
+ if (!al || !al->sym || strcmp(al->sym, "test_loop"))
+ return 1;
+
+ return 0;
+}
+EOF
+
+test_dlfilter() {
+ echo "Basic --dlfilter test"
+ # Generate perf.data file
+ if ! perf record -o "${perfdata}" perf test -w thloop 1 2> /dev/null
+ then
+ echo "Basic --dlfilter test [Failed record]"
+ err=1
+ return
+ fi
+
+ # Build the dlfilter
+ if ! cc -c -I ${shelldir}/../../include -fpic -x c "${dlfilter_c}" -o "${dlfilter_so}.o"
+ then
+ echo "Basic --dlfilter test [Skip - failed to build dlfilter object]"
+ err=2
+ return
+ fi
+
+ if ! cc -shared -o "${dlfilter_so}" "${dlfilter_so}.o"
+ then
+ echo "Basic --dlfilter test [Skip - failed to link dlfilter shared object]"
+ err=2
+ return
+ fi
+
+ # Check that the output contains "test_loop" and nothing else
+ if ! perf script -i "${perfdata}" --dlfilter "${dlfilter_so}" | grep -q "test_loop"
+ then
+ echo "Basic --dlfilter test [Failed missing output]"
+ err=1
+ return
+ fi
+
+ # The filter should filter out everything except test_loop, so ensure no other symbols are present
+ # This is a simple check; we could be more rigorous
+ if perf script -i "${perfdata}" --dlfilter "${dlfilter_so}" | grep -v "test_loop" | grep -q "perf"
+ then
+ echo "Basic --dlfilter test [Failed filtering]"
+ err=1
+ return
+ fi
+
+ echo "Basic --dlfilter test [Success]"
+}
+
+test_dlfilter
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/script_perl.sh b/tools/perf/tests/shell/script_perl.sh
new file mode 100755
index 000000000..b6d65b6fb
--- /dev/null
+++ b/tools/perf/tests/shell/script_perl.sh
@@ -0,0 +1,102 @@
+#!/bin/bash
+# perf script perl tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+# set PERF_EXEC_PATH to find scripts in the source directory
+perfdir=$(dirname "$0")/../..
+if [ -e "$perfdir/scripts/perl/Perf-Trace-Util" ]; then
+ export PERF_EXEC_PATH=$perfdir
+fi
+
+
+perfdata=$(mktemp /tmp/__perf_test_script_perl.perf.data.XXXXX)
+generated_script=$(mktemp /tmp/__perf_test_script.XXXXX.pl)
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${generated_script}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup TERM INT
+trap cleanup EXIT
+
+check_perl_support() {
+ if perf check feature -q libperl; then
+ return 0
+ fi
+ echo "perf script perl test [Skipped: no libperl support]"
+ return 2
+}
+
+test_script() {
+ local event_name=$1
+ local expected_output=$2
+ local record_opts=$3
+
+ echo "Testing event: $event_name"
+
+ # Try to record. If this fails, it might be permissions or lack of support.
+ # We return 2 to indicate "skip this event" rather than "fail test".
+ if ! perf record -o "${perfdata}" -e "$event_name" $record_opts -- perf test -w thloop > /dev/null 2>&1; then
+ echo "perf script perl test [Skipped: failed to record $event_name]"
+ return 2
+ fi
+
+ echo "Generating perl script..."
+ if ! perf script -i "${perfdata}" -g "${generated_script}"; then
+ echo "perf script perl test [Failed: script generation for $event_name]"
+ return 1
+ fi
+
+ if [ ! -f "${generated_script}" ]; then
+ echo "perf script perl test [Failed: script not generated for $event_name]"
+ return 1
+ fi
+
+ echo "Executing perl script..."
+ output=$(perf script -i "${perfdata}" -s "${generated_script}" 2>&1)
+
+ if echo "$output" | grep -q "$expected_output"; then
+ echo "perf script perl test [Success: $event_name triggered $expected_output]"
+ return 0
+ else
+ echo "perf script perl test [Failed: $event_name did not trigger $expected_output]"
+ echo "Output was:"
+ echo "$output" | head -n 20
+ return 1
+ fi
+}
+
+check_perl_support || exit 2
+
+# Try tracepoint first
+test_script "sched:sched_switch" "sched::sched_switch" "-c 1" && res=0 || res=$?
+
+if [ $res -eq 0 ]; then
+ exit 0
+elif [ $res -eq 1 ]; then
+ exit 1
+fi
+
+# If tracepoint skipped (res=2), try task-clock
+# For generic events like task-clock, the generated script uses process_event()
+# which dumps data using Data::Dumper. We check for "$VAR1" which is standard Dumper output.
+test_script "task-clock" "\$VAR1" "-c 100" && res=0 || res=$?
+
+if [ $res -eq 0 ]; then
+ exit 0
+elif [ $res -eq 1 ]; then
+ exit 1
+fi
+
+# If both skipped
+echo "perf script perl test [Skipped: Could not record tracepoint or task-clock]"
+exit 2
diff --git a/tools/perf/tests/shell/script_python.sh b/tools/perf/tests/shell/script_python.sh
new file mode 100755
index 000000000..6bc66074a
--- /dev/null
+++ b/tools/perf/tests/shell/script_python.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+# perf script python tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+# set PERF_EXEC_PATH to find scripts in the source directory
+perfdir=$(dirname "$0")/../..
+if [ -e "$perfdir/scripts/python/Perf-Trace-Util" ]; then
+ export PERF_EXEC_PATH=$perfdir
+fi
+
+
+perfdata=$(mktemp /tmp/__perf_test_script_python.perf.data.XXXXX)
+generated_script=$(mktemp /tmp/__perf_test_script.XXXXX.py)
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${generated_script}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup TERM INT
+trap cleanup EXIT
+
+check_python_support() {
+ if perf check feature -q libpython; then
+ return 0
+ fi
+ echo "perf script python test [Skipped: no libpython support]"
+ return 2
+}
+
+test_script() {
+ local event_name=$1
+ local expected_output=$2
+ local record_opts=$3
+
+ echo "Testing event: $event_name"
+
+ # Try to record. If this fails, it might be permissions or lack of
+ # support. Return 2 to indicate "skip this event" rather than "fail
+ # test".
+ if ! perf record -o "${perfdata}" -e "$event_name" $record_opts -- perf test -w thloop > /dev/null 2>&1; then
+ echo "perf script python test [Skipped: failed to record $event_name]"
+ return 2
+ fi
+
+ echo "Generating python script..."
+ if ! perf script -i "${perfdata}" -g "${generated_script}"; then
+ echo "perf script python test [Failed: script generation for $event_name]"
+ return 1
+ fi
+
+ if [ ! -f "${generated_script}" ]; then
+ echo "perf script python test [Failed: script not generated for $event_name]"
+ return 1
+ fi
+
+ # Perf script -g python doesn't generate process_event for generic
+ # events so append it manually to test that the callback works.
+ if ! grep -q "def process_event" "${generated_script}"; then
+ cat <<EOF >> "${generated_script}"
+
+def process_event(param_dict):
+ print("param_dict: %s" % param_dict)
+EOF
+ fi
+
+ echo "Executing python script..."
+ output=$(perf script -i "${perfdata}" -s "${generated_script}" 2>&1)
+
+ if echo "$output" | grep -q "$expected_output"; then
+ echo "perf script python test [Success: $event_name triggered $expected_output]"
+ return 0
+ else
+ echo "perf script python test [Failed: $event_name did not trigger $expected_output]"
+ echo "Output was:"
+ echo "$output" | head -n 20
+ return 1
+ fi
+}
+
+check_python_support || exit 2
+
+# Try tracepoint first
+test_script "sched:sched_switch" "sched__sched_switch" "-c 1" && res=0 || res=$?
+
+if [ $res -eq 0 ]; then
+ exit 0
+elif [ $res -eq 1 ]; then
+ exit 1
+fi
+
+# If tracepoint skipped (res=2), try task-clock
+# For generic events like task-clock, the generated script uses process_event()
+# which prints the param_dict.
+test_script "task-clock" "param_dict" "-c 100" && res=0 || res=$?
+
+if [ $res -eq 0 ]; then
+ exit 0
+elif [ $res -eq 1 ]; then
+ exit 1
+fi
+
+# If both skipped
+echo "perf script python test [Skipped: Could not record tracepoint or task-clock]"
+exit 2
diff --git a/tools/perf/tests/shell/stat+csv_output.sh b/tools/perf/tests/shell/stat+csv_output.sh
new file mode 100755
index 000000000..cd6fff597
--- /dev/null
+++ b/tools/perf/tests/shell/stat+csv_output.sh
@@ -0,0 +1,92 @@
+#!/bin/bash
+# perf stat CSV output linter
+# 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 ','.
+
+set -e
+
+# shellcheck source=lib/stat_output.sh
+. "$(dirname $0)"/lib/stat_output.sh
+
+csv_sep=@
+
+stat_output=$(mktemp /tmp/__perf_test.stat_output.csv.XXXXX)
+
+cleanup() {
+ rm -f "${stat_output}"
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+function commachecker()
+{
+ local -i cnt=0
+ local exp=0
+
+ case "$1"
+ in "--no-args") exp=6
+ ;; "--system-wide") exp=6
+ ;; "--event") exp=6
+ ;; "--interval") exp=7
+ ;; "--per-thread") exp=7
+ ;; "--system-wide-no-aggr") exp=7
+ [ "$(uname -m)" = "s390x" ] && exp='^[6-7]$'
+ ;; "--per-core") exp=8
+ ;; "--per-socket") exp=8
+ ;; "--per-node") exp=8
+ ;; "--per-die") exp=8
+ ;; "--per-cluster") exp=8
+ ;; "--per-cache") exp=8
+ ;; "--metric-only") exp=1
+ esac
+
+ while read line
+ do
+ # Ignore initial "started on" comment.
+ x=${line:0:1}
+ [ "$x" = "#" ] && continue
+ # Ignore initial blank line.
+ [ "$line" = "" ] && continue
+
+ # Count the number of commas
+ x=$(echo $line | tr -d -c $csv_sep)
+ cnt="${#x}"
+ # echo $line $cnt
+ [[ ! "$cnt" =~ $exp ]] && {
+ echo "wrong number of fields. expected $exp in $line" 1>&2
+ exit 1;
+ }
+ done < "${stat_output}"
+ return 0
+}
+
+perf_cmd="-x$csv_sep -o ${stat_output}"
+
+skip_test=$(check_for_topology)
+check_no_args "CSV" "$perf_cmd"
+check_system_wide "CSV" "$perf_cmd"
+check_interval "CSV" "$perf_cmd"
+check_event "CSV" "$perf_cmd"
+check_per_thread "CSV" "$perf_cmd"
+check_per_node "CSV" "$perf_cmd"
+check_metric_only "CSV" "$perf_cmd"
+if [ $skip_test -ne 1 ]
+then
+ check_system_wide_no_aggr "CSV" "$perf_cmd"
+ check_per_core "CSV" "$perf_cmd"
+ check_per_cache_instance "CSV" "$perf_cmd"
+ check_per_cluster "CSV" "$perf_cmd"
+ check_per_die "CSV" "$perf_cmd"
+ check_per_socket "CSV" "$perf_cmd"
+else
+ echo "[Skip] Skipping tests for system_wide_no_aggr, per_core, per_die and per_socket since socket id exposed via topology is invalid"
+fi
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/stat+csv_summary.sh b/tools/perf/tests/shell/stat+csv_summary.sh
new file mode 100755
index 000000000..9a4353db3
--- /dev/null
+++ b/tools/perf/tests/shell/stat+csv_summary.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+# perf stat csv summary test
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+#
+# 1.001364330 9224197 cycles 8012885033 100.00
+# summary 9224197 cycles 8012885033 100.00
+#
+perf stat -e cycles -x' ' -I1000 --interval-count 1 --summary 2>&1 | \
+grep -e summary | \
+while read summary _ _ _ _
+do
+ if [ $summary != "summary" ]; then
+ exit 1
+ fi
+done
+
+#
+# 1.001360298 9148534 cycles 8012853854 100.00
+#9148534 cycles 8012853854 100.00
+#
+perf stat -e cycles -x' ' -I1000 --interval-count 1 --summary --no-csv-summary 2>&1 | \
+grep -e summary | \
+while read _ _ _ _
+do
+ exit 1
+done
+
+exit 0
diff --git a/tools/perf/tests/shell/stat+event_uniquifying.sh b/tools/perf/tests/shell/stat+event_uniquifying.sh
new file mode 100755
index 000000000..b5dec6b6d
--- /dev/null
+++ b/tools/perf/tests/shell/stat+event_uniquifying.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+# perf stat events uniquifying
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+stat_output=$(mktemp /tmp/__perf_test.stat_output.XXXXX)
+
+cleanup() {
+ rm -f "${stat_output}"
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_event_uniquifying() {
+ echo "Uniquification of PMU sysfs events test"
+
+ # Read events from perf list with and without -v. With -v the duplicate PMUs
+ # aren't deduplicated. Note, json events are listed by perf list without a
+ # PMU.
+ read -ra pmu_events <<< "$(perf list --raw pmu)"
+ read -ra pmu_v_events <<< "$(perf list -v --raw pmu)"
+ # For all non-deduplicated events.
+ for pmu_v_event in "${pmu_v_events[@]}"; do
+ # If the event matches an event in the deduplicated events then it musn't
+ # be an event with duplicate PMUs, continue the outer loop.
+ for pmu_event in "${pmu_events[@]}"; do
+ if [[ "$pmu_v_event" == "$pmu_event" ]]; then
+ continue 2
+ fi
+ done
+ # Strip the suffix from the non-deduplicated event's PMU.
+ event=$(echo "$pmu_v_event" | sed -E 's/_[0-9]+//')
+ for pmu_event in "${pmu_events[@]}"; do
+ if [[ "$event" == "$pmu_event" ]]; then
+ echo "Testing event ${event} is uniquified to ${pmu_v_event}"
+ if ! perf stat -e "$event" -A -o ${stat_output} -- true; then
+ echo "Error running perf stat for event '$event' [Skip]"
+ if [ $err = 0 ]; then
+ err=2
+ fi
+ continue
+ fi
+ # Ensure the non-deduplicated event appears in the output.
+ if ! grep -q "${pmu_v_event}" "${stat_output}"; then
+ echo "Uniquification of PMU sysfs events test [Failed]"
+ cat "${stat_output}"
+ err=1
+ fi
+ break
+ fi
+ done
+ done
+}
+
+test_event_uniquifying
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/stat+json_output.sh b/tools/perf/tests/shell/stat+json_output.sh
new file mode 100755
index 000000000..85d1ad718
--- /dev/null
+++ b/tools/perf/tests/shell/stat+json_output.sh
@@ -0,0 +1,236 @@
+#!/bin/bash
+# perf stat JSON output linter
+# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
+# Checks various perf stat JSON output commands for the
+# correct number of fields.
+
+set -e
+
+skip_test=0
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/setup_python.sh
+. "${shelldir}"/lib/setup_python.sh
+pythonchecker=$(dirname $0)/lib/perf_json_output_lint.py
+
+stat_output=$(mktemp /tmp/__perf_test.stat_output.json.XXXXX)
+
+cleanup() {
+ rm -f "${stat_output}"
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+# Return true if perf_event_paranoid is > $1 and not running as root.
+function ParanoidAndNotRoot()
+{
+ [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
+}
+
+check_no_args()
+{
+ echo -n "Checking json output: no args "
+ perf stat -j -o "${stat_output}" true
+ $PYTHON $pythonchecker --no-args --file "${stat_output}"
+ echo "[Success]"
+}
+
+check_system_wide()
+{
+ echo -n "Checking json output: system wide "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoia and not root"
+ return
+ fi
+ perf stat -j -a -o "${stat_output}" true
+ $PYTHON $pythonchecker --system-wide --file "${stat_output}"
+ echo "[Success]"
+}
+
+check_system_wide_no_aggr()
+{
+ echo -n "Checking json output: system wide no aggregation "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoia and not root"
+ return
+ fi
+ perf stat -j -A -a --no-merge -o "${stat_output}" true
+ $PYTHON $pythonchecker --system-wide-no-aggr --file "${stat_output}"
+ echo "[Success]"
+}
+
+check_interval()
+{
+ echo -n "Checking json output: interval "
+ perf stat -j -I 1000 -o "${stat_output}" true
+ $PYTHON $pythonchecker --interval --file "${stat_output}"
+ echo "[Success]"
+}
+
+
+check_event()
+{
+ echo -n "Checking json output: event "
+ perf stat -j -e cpu-clock -o "${stat_output}" true
+ $PYTHON $pythonchecker --event --file "${stat_output}"
+ echo "[Success]"
+}
+
+check_per_core()
+{
+ echo -n "Checking json output: per core "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoia and not root"
+ return
+ fi
+ perf stat -j --per-core -a -o "${stat_output}" true
+ $PYTHON $pythonchecker --per-core --file "${stat_output}"
+ echo "[Success]"
+}
+
+check_per_thread()
+{
+ echo -n "Checking json output: per thread "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoia and not root"
+ return
+ fi
+ perf stat -j --per-thread -p $$ -o "${stat_output}" true
+ $PYTHON $pythonchecker --per-thread --file "${stat_output}"
+ echo "[Success]"
+}
+
+check_per_cache_instance()
+{
+ echo -n "Checking json output: per cache_instance "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoia and not root"
+ return
+ fi
+ perf stat -j --per-cache -a true 2>&1 | $PYTHON $pythonchecker --per-cache
+ echo "[Success]"
+}
+
+check_per_cluster()
+{
+ echo -n "Checking json output: per cluster "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoia and not root"
+ return
+ fi
+ perf stat -j --per-cluster -a true 2>&1 | $PYTHON $pythonchecker --per-cluster
+ echo "[Success]"
+}
+
+check_per_die()
+{
+ echo -n "Checking json output: per die "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoia and not root"
+ return
+ fi
+ perf stat -j --per-die -a -o "${stat_output}" true
+ $PYTHON $pythonchecker --per-die --file "${stat_output}"
+ echo "[Success]"
+}
+
+check_per_node()
+{
+ echo -n "Checking json output: per node "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoia and not root"
+ return
+ fi
+ perf stat -j --per-node -a -o "${stat_output}" true
+ $PYTHON $pythonchecker --per-node --file "${stat_output}"
+ echo "[Success]"
+}
+
+check_per_socket()
+{
+ echo -n "Checking json output: per socket "
+ if ParanoidAndNotRoot 0
+ then
+ echo "[Skip] paranoia and not root"
+ return
+ fi
+ perf stat -j --per-socket -a -o "${stat_output}" true
+ $PYTHON $pythonchecker --per-socket --file "${stat_output}"
+ echo "[Success]"
+}
+
+check_metric_only()
+{
+ echo -n "Checking json output: metric only "
+ if [ "$(uname -m)" = "s390x" ] && ! grep '^facilities' /proc/cpuinfo | grep -qw 67
+ then
+ echo "[Skip] CPU-measurement counter facility not installed"
+ return
+ fi
+ perf stat -j --metric-only -M page_faults_per_second -o "${stat_output}" true
+ $PYTHON $pythonchecker --metric-only --file "${stat_output}"
+ echo "[Success]"
+}
+
+# The perf stat options for per-socket, per-core, per-die
+# and -A ( no_aggr mode ) uses the info fetched from this
+# directory: "/sys/devices/system/cpu/cpu*/topology". For
+# example, socket value is fetched from "physical_package_id"
+# file in topology directory.
+# Reference: cpu__get_topology_int in util/cpumap.c
+# If the platform doesn't expose topology information, values
+# will be set to -1. For example, incase of pSeries platform
+# of powerpc, value for "physical_package_id" is restricted
+# and set to -1. Check here validates the socket-id read from
+# topology file before proceeding further
+
+FILE_LOC="/sys/devices/system/cpu/cpu*/topology/"
+FILE_NAME="physical_package_id"
+
+check_for_topology()
+{
+ if ! ParanoidAndNotRoot 0
+ then
+ socket_file=`ls $FILE_LOC/$FILE_NAME | head -n 1`
+ [ -z $socket_file ] && return 0
+ socket_id=`cat $socket_file`
+ [ $socket_id == -1 ] && skip_test=1
+ return 0
+ fi
+}
+
+check_for_topology
+check_no_args
+check_system_wide
+check_interval
+check_event
+check_per_thread
+check_per_node
+check_metric_only
+if [ $skip_test -ne 1 ]
+then
+ check_system_wide_no_aggr
+ check_per_core
+ check_per_cache_instance
+ check_per_cluster
+ check_per_die
+ check_per_socket
+else
+ echo "[Skip] Skipping tests for system_wide_no_aggr, per_core, per_die and per_socket since socket id exposed via topology is invalid"
+fi
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/stat+shadow_stat.sh b/tools/perf/tests/shell/stat+shadow_stat.sh
new file mode 100755
index 000000000..cabbbf17c
--- /dev/null
+++ b/tools/perf/tests/shell/stat+shadow_stat.sh
@@ -0,0 +1,99 @@
+#!/bin/bash
+# perf stat metrics (shadow stat) test
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+THRESHOLD=0.015
+
+# skip if system-wide mode is forbidden
+perf stat -a true > /dev/null 2>&1 || exit 2
+
+# skip if on hybrid platform
+perf stat -a -e cycles sleep 1 2>&1 | grep -e cpu_core && exit 2
+
+test_global_aggr()
+{
+ perf stat -a --no-big-num -M insn_per_cycle sleep 1 2>&1 | \
+ grep -e cycles -e instructions | \
+ while read num evt _ ipc rest
+ do
+ # skip not counted events
+ if [ "$num" = "<not" ]; then
+ continue
+ fi
+
+ # save cycles count
+ if [ "$evt" = "cycles" ]; then
+ cyc=$num
+ continue
+ fi
+
+ # skip if no cycles
+ if [ -z "$cyc" ]; then
+ continue
+ fi
+
+ # use printf for rounding and a leading zero
+ res=`echo $num $cyc | awk '{printf "%.2f", $1 / $2}'`
+ if [ "$ipc" != "$res" ]; then
+ # check the difference from the real result for FP imperfections
+ diff=`echo $ipc $res $THRESHOLD | \
+ awk '{x = ($1 - $2) < 0 ? ($2 - $1) : ($1 - $2); print (x > $3)}'`
+
+ if [ $diff -eq 1 ]; then
+ echo "IPC is different: $res != $ipc ($num / $cyc)"
+ exit 1
+ fi
+
+ echo "Warning: Difference of IPC is under the threshold"
+ fi
+ done
+}
+
+test_no_aggr()
+{
+ perf stat -a -A --no-big-num -M insn_per_cycle sleep 1 2>&1 | \
+ grep ^CPU | \
+ while read cpu num evt _ ipc rest
+ do
+ # skip not counted events
+ if [ "$num" = "<not" ]; then
+ continue
+ fi
+
+ # save cycles count
+ if [ "$evt" = "cycles" ]; then
+ results="$results $cpu:$num"
+ continue
+ fi
+
+ cyc=${results##* $cpu:}
+ cyc=${cyc%% *}
+
+ # skip if no cycles
+ if [ -z "$cyc" ]; then
+ continue
+ fi
+
+ # use printf for rounding and a leading zero
+ res=`echo $num $cyc | awk '{printf "%.2f", $1 / $2}'`
+ if [ "$ipc" != "$res" ]; then
+ # check difference from the real result for FP imperfections
+ diff=`echo $ipc $res $THRESHOLD | \
+ awk '{x = ($1 - $2) < 0 ? ($2 - $1) : ($1 - $2); print (x > $3)}'`
+
+ if [ $diff -eq 1 ]; then
+ echo "IPC is different: $res != $ipc ($num / $cyc)"
+ exit 1
+ fi
+
+ echo "Warning: Difference of IPC is under the threshold"
+ fi
+ done
+}
+
+test_global_aggr
+test_no_aggr
+
+exit 0
diff --git a/tools/perf/tests/shell/stat+std_output.sh b/tools/perf/tests/shell/stat+std_output.sh
new file mode 100755
index 000000000..9c4b92ecf
--- /dev/null
+++ b/tools/perf/tests/shell/stat+std_output.sh
@@ -0,0 +1,122 @@
+#!/bin/bash
+# perf stat STD output linter
+# SPDX-License-Identifier: GPL-2.0
+# Tests various perf stat STD output commands for
+# default event and metricgroup
+
+set -e
+
+# shellcheck source=lib/stat_output.sh
+. "$(dirname $0)"/lib/stat_output.sh
+
+stat_output=$(mktemp /tmp/__perf_test.stat_output.std.XXXXX)
+
+event_name=(cpu-clock task-clock context-switches cpu-migrations page-faults stalled-cycles-frontend stalled-cycles-backend cycles instructions branches branch-misses)
+event_metric=("CPUs_utilized" "CPUs_utilized" "cs/sec" "migrations/sec" "faults/sec" "frontend_cycles_idle" "backend_cycles_idle" "GHz" "insn_per_cycle" "/sec" "branch_miss_rate")
+skip_metric=("tma_" "TopdownL1")
+
+cleanup() {
+ rm -f "${stat_output}"
+
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+function commachecker()
+{
+ local prefix=1
+ local -i metric_only=0
+
+ case "$1"
+ in "--interval") prefix=2
+ ;; "--per-thread") prefix=2
+ ;; "--system-wide-no-aggr") prefix=2
+ ;; "--per-core") prefix=3
+ ;; "--per-socket") prefix=3
+ ;; "--per-node") prefix=3
+ ;; "--per-die") prefix=3
+ ;; "--per-cache") prefix=3
+ ;; "--per-cluster") prefix=3
+ ;; "--metric-only") metric_only=1
+ esac
+
+ while read line
+ do
+ # Ignore initial "started on" comment.
+ x=${line:0:1}
+ [ "$x" = "#" ] && continue
+ # Ignore initial blank line.
+ [ "$line" = "" ] && continue
+ # Ignore "Performance counter stats"
+ x=${line:0:25}
+ [ "$x" = "Performance counter stats" ] && continue
+ # Ignore "seconds time elapsed" and break
+ [[ "$line" == *"time elapsed"* ]] && break
+
+ main_body=$(echo $line | cut -d' ' -f$prefix-)
+ x=${main_body%#*}
+ [ "$x" = "" ] && continue
+
+ # Check metric only - if it has a non-empty result
+ [ $metric_only -eq 1 ] && return 0
+
+ # Skip metrics without event name
+ y=${main_body#*#}
+ for i in "${!skip_metric[@]}"; do
+ [[ "$y" == *"${skip_metric[$i]}"* ]] && break
+ done
+ [[ "$y" == *"${skip_metric[$i]}"* ]] && continue
+
+ # Check default event
+ for i in "${!event_name[@]}"; do
+ [[ "$x" == *"${event_name[$i]}"* ]] && break
+ done
+
+ [[ ! "$x" == *"${event_name[$i]}"* ]] && {
+ echo "Unknown event name in $line" 1>&2
+ exit 1;
+ }
+
+ # Check event metric if it exists
+ [[ ! "$main_body" == *"#"* ]] && continue
+ [[ ! "$main_body" == *"${event_metric[$i]}"* ]] && {
+ echo "wrong event metric. expected ${event_metric[$i]} in $line" 1>&2
+ exit 1;
+ }
+ done < "${stat_output}"
+
+ if [ $metric_only -ne 1 ]
+ then
+ echo "Missing metric only output in:"
+ cat "${stat_output}"
+ fi
+ return 0
+}
+
+perf_cmd="-o ${stat_output}"
+
+skip_test=$(check_for_topology)
+check_no_args "STD" "$perf_cmd"
+check_system_wide "STD" "$perf_cmd"
+check_interval "STD" "$perf_cmd"
+check_per_thread "STD" "$perf_cmd"
+check_per_node "STD" "$perf_cmd"
+check_metric_only "STD" "$perf_cmd"
+if [ $skip_test -ne 1 ]
+then
+ check_system_wide_no_aggr "STD" "$perf_cmd"
+ check_per_core "STD" "$perf_cmd"
+ check_per_cache_instance "STD" "$perf_cmd"
+ check_per_cluster "STD" "$perf_cmd"
+ check_per_die "STD" "$perf_cmd"
+ check_per_socket "STD" "$perf_cmd"
+else
+ echo "[Skip] Skipping tests for system_wide_no_aggr, per_core, per_die and per_socket since socket id exposed via topology is invalid"
+fi
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/stat.sh b/tools/perf/tests/shell/stat.sh
new file mode 100755
index 000000000..298ce454b
--- /dev/null
+++ b/tools/perf/tests/shell/stat.sh
@@ -0,0 +1,614 @@
+#!/bin/bash
+# perf stat tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+stat_output=$(mktemp /tmp/perf-stat-test-output.XXXXX)
+
+cleanup() {
+ rm -f "${stat_output}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+
+trap trap_cleanup EXIT TERM INT
+
+test_default_stat() {
+ echo "Basic stat command test"
+ if ! perf stat true 2>&1 | grep -E -q "Performance counter stats for 'true':"
+ then
+ echo "Basic stat command test [Failed]"
+ err=1
+ return
+ fi
+ echo "Basic stat command test [Success]"
+}
+
+test_null_stat() {
+ echo "Null stat command test"
+ if ! perf stat --null true 2>&1 | grep -E -q "Performance counter stats for 'true':"
+ then
+ echo "Null stat command test [Failed]"
+ err=1
+ return
+ fi
+ echo "Null stat command test [Success]"
+}
+
+find_offline_cpu() {
+ for i in $(seq 1 4096)
+ do
+ if [[ ! -f /sys/devices/system/cpu/cpu$i/online || \
+ $(cat /sys/devices/system/cpu/cpu$i/online) == "0" ]]
+ then
+ echo $i
+ return
+ fi
+ done
+ echo "Failed to find offline CPU"
+ exit 1
+}
+
+test_offline_cpu_stat() {
+ cpu=$(find_offline_cpu)
+ echo "Offline CPU stat command test (cpu $cpu)"
+ if ! perf stat "-C$cpu" -e cycles true 2>&1 | grep -E -q "No supported events found."
+ then
+ echo "Offline CPU stat command test [Failed]"
+ err=1
+ return
+ fi
+ echo "Offline CPU stat command test [Success]"
+}
+
+test_stat_record_report() {
+ echo "stat record and report test"
+ if ! perf stat record -e task-clock -o - true | perf stat report -i - 2>&1 | \
+ grep -E -q "Performance counter stats for 'pipe':"
+ then
+ echo "stat record and report test [Failed]"
+ err=1
+ return
+ fi
+ echo "stat record and report test [Success]"
+}
+
+test_stat_record_script() {
+ echo "stat record and script test"
+ if ! perf stat record -e task-clock -o - true | perf script -i - 2>&1 | \
+ grep -E -q "CPU[[:space:]]+THREAD[[:space:]]+VAL[[:space:]]+ENA[[:space:]]+RUN[[:space:]]+TIME[[:space:]]+EVENT"
+ then
+ echo "stat record and script test [Failed]"
+ err=1
+ return
+ fi
+ echo "stat record and script test [Success]"
+}
+
+test_stat_repeat_weak_groups() {
+ echo "stat repeat weak groups test"
+ if ! perf stat -e '{cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles}' \
+ true 2>&1 | grep -q 'seconds time elapsed'
+ then
+ echo "stat repeat weak groups test [Skipped event parsing failed]"
+ return
+ fi
+ if ! perf stat -r2 -e '{cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles}:W' \
+ true > /dev/null 2>&1
+ then
+ echo "stat repeat weak groups test [Failed]"
+ err=1
+ return
+ fi
+ echo "stat repeat weak groups test [Success]"
+}
+
+test_topdown_groups() {
+ # Topdown events must be grouped with the slots event first. Test that
+ # parse-events reorders this.
+ echo "Topdown event group test"
+ if ! perf stat -e '{slots,topdown-retiring}' true > /dev/null 2>&1
+ then
+ echo "Topdown event group test [Skipped event parsing failed]"
+ return
+ fi
+ td_err=0
+ do_topdown_group_test() {
+ events=$1
+ failure=$2
+ if perf stat -e "$events" true 2>&1 | grep -E -q "<not supported>"
+ then
+ echo "Topdown event group test [Failed $failure for '$events']"
+ td_err=1
+ return
+ fi
+ }
+ do_topdown_group_test "{slots,topdown-retiring}" "events not supported"
+ do_topdown_group_test "{instructions,r400,r8000}" "raw format slots not reordered first"
+ filler_events=("instructions" "cycles"
+ "context-switches" "faults")
+ for ((i = 0; i < ${#filler_events[@]}; i+=2))
+ do
+ filler1=${filler_events[i]}
+ filler2=${filler_events[i+1]}
+ do_topdown_group_test "$filler1,topdown-retiring,slots" \
+ "slots not reordered first in no-group case"
+ do_topdown_group_test "slots,$filler1,topdown-retiring" \
+ "topdown metrics event not reordered in no-group case"
+ do_topdown_group_test "{$filler1,topdown-retiring,slots}" \
+ "slots not reordered first in single group case"
+ do_topdown_group_test "{$filler1,slots},topdown-retiring" \
+ "topdown metrics event not move into slots group"
+ do_topdown_group_test "topdown-retiring,{$filler1,slots}" \
+ "topdown metrics event not move into slots group last"
+ do_topdown_group_test "{$filler1,slots},{topdown-retiring}" \
+ "topdown metrics group not merge into slots group"
+ do_topdown_group_test "{topdown-retiring},{$filler1,slots}" \
+ "topdown metrics group not merge into slots group last"
+ do_topdown_group_test "{$filler1,slots},$filler2,topdown-retiring" \
+ "non-adjacent topdown metrics group not move into slots group"
+ do_topdown_group_test "$filler2,topdown-retiring,{$filler1,slots}" \
+ "non-adjacent topdown metrics group not move into slots group last"
+ do_topdown_group_test "{$filler1,slots},{$filler2,topdown-retiring}" \
+ "metrics group not merge into slots group"
+ do_topdown_group_test "{$filler1,topdown-retiring},{$filler2,slots}" \
+ "metrics group not merge into slots group last"
+ done
+ if test "$td_err" -eq 0
+ then
+ echo "Topdown event group test [Success]"
+ else
+ err="$td_err"
+ fi
+}
+
+test_topdown_weak_groups() {
+ # Weak groups break if the perf_event_open of multiple grouped events
+ # fails. Breaking a topdown group causes the events to fail. Test a very large
+ # grouping to see that the topdown events aren't broken out.
+ echo "Topdown weak groups test"
+ ok_grouping="{slots,topdown-bad-spec,topdown-be-bound,topdown-fe-bound,topdown-retiring},branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,mem-loads,mem-stores,ref-cycles,cache-misses,cache-references"
+ if ! perf stat --no-merge -e "$ok_grouping" true > /dev/null 2>&1
+ then
+ echo "Topdown weak groups test [Skipped event parsing failed]"
+ return
+ fi
+ group_needs_break="{slots,topdown-bad-spec,topdown-be-bound,topdown-fe-bound,topdown-retiring,branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,mem-loads,mem-stores,ref-cycles,cache-misses,cache-references}:W"
+ if perf stat --no-merge -e "$group_needs_break" true 2>&1 | grep -E -q "<not supported>"
+ then
+ echo "Topdown weak groups test [Failed events not supported]"
+ err=1
+ return
+ fi
+ echo "Topdown weak groups test [Success]"
+}
+
+test_cputype() {
+ # Test --cputype argument.
+ echo "cputype test"
+
+ # Bogus PMU should fail.
+ if perf stat --cputype="123" -e instructions true > /dev/null 2>&1
+ then
+ echo "cputype test [Bogus PMU didn't fail]"
+ err=1
+ return
+ fi
+
+ # Find a known PMU for cputype.
+ pmu=""
+ devs="/sys/bus/event_source/devices"
+ for i in $devs/cpu $devs/cpu_atom $devs/armv8_pmuv3_0 $devs/armv8_cortex_*
+ do
+ i_base=$(basename "$i")
+ if test -d "$i"
+ then
+ pmu="$i_base"
+ break
+ fi
+ if perf stat -e "$i_base/instructions/" true > /dev/null 2>&1
+ then
+ pmu="$i_base"
+ break
+ fi
+ done
+ if test "x$pmu" = "x"
+ then
+ echo "cputype test [Skipped known PMU not found]"
+ return
+ fi
+
+ # Test running with cputype produces output.
+ if ! perf stat --cputype="$pmu" -e instructions true 2>&1 | grep -E -q "instructions"
+ then
+ echo "cputype test [Failed count missed with given filter]"
+ err=1
+ return
+ fi
+ echo "cputype test [Success]"
+}
+
+test_hybrid() {
+ # Test the default stat command on hybrid devices opens one cycles event for
+ # each CPU type.
+ echo "hybrid test"
+
+ # Count the number of core PMUs, assume minimum of 1
+ pmus=$(ls /sys/bus/event_source/devices/*/cpus 2>/dev/null | wc -l)
+ if [ "$pmus" -lt 1 ]
+ then
+ pmus=1
+ fi
+
+ # Run default Perf stat
+ cycles_events=$(perf stat -a -- sleep 0.1 2>&1 | grep -E "/cpu-cycles/[uH]*| cpu-cycles[:uH]* " | wc -l)
+
+ # The expectation is that default output will have a cycles events on each
+ # hybrid PMU. In situations with no cycles PMU events, like virtualized, this
+ # can fall back to task-clock and so the end count may be 0. Fail if neither
+ # condition holds.
+ if [ "$pmus" -ne "$cycles_events" ] && [ "0" -ne "$cycles_events" ]
+ then
+ echo "hybrid test [Found $pmus PMUs but $cycles_events cycles events. Failed]"
+ err=1
+ return
+ fi
+ echo "hybrid test [Success]"
+}
+
+test_stat_cpu() {
+ echo "stat -C <cpu> test"
+ # Test the full online CPU list (ranges and lists)
+ online_cpus=$(cat /sys/devices/system/cpu/online)
+ if ! perf stat -C "$online_cpus" -a true > "${stat_output}" 2>&1
+ then
+ echo "stat -C <cpu> test [Failed - command failed for cpus $online_cpus]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ if ! grep -E -q "Performance counter stats for" "${stat_output}"
+ then
+ echo "stat -C <cpu> test [Failed - missing output for cpus $online_cpus]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ # Test each individual online CPU
+ for cpu_dir in /sys/devices/system/cpu/cpu[0-9]*; do
+ cpu=${cpu_dir##*/cpu}
+ # Check if online
+ if [ -f "$cpu_dir/online" ] && [ "$(cat "$cpu_dir/online")" -eq 0 ]
+ then
+ continue
+ fi
+
+ if ! perf stat -C "$cpu" -a true > "${stat_output}" 2>&1
+ then
+ echo "stat -C <cpu> test [Failed - command failed for cpu $cpu]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+ if ! grep -E -q "Performance counter stats for" "${stat_output}"
+ then
+ echo "stat -C <cpu> test [Failed - missing output for cpu $cpu]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+ done
+
+ # Test synthetic list and range if cpu0 and cpu1 are online
+ c0_online=0
+ c1_online=0
+ if [ -d "/sys/devices/system/cpu/cpu0" ]
+ then
+ if [ ! -f "/sys/devices/system/cpu/cpu0/online" ] || [ "$(cat /sys/devices/system/cpu/cpu0/online)" -eq 1 ]
+ then
+ c0_online=1
+ fi
+ fi
+ if [ -d "/sys/devices/system/cpu/cpu1" ]
+ then
+ if [ ! -f "/sys/devices/system/cpu/cpu1/online" ] || [ "$(cat /sys/devices/system/cpu/cpu1/online)" -eq 1 ]
+ then
+ c1_online=1
+ fi
+ fi
+
+ if [ $c0_online -eq 1 ] && [ $c1_online -eq 1 ]
+ then
+ # Test list "0,1"
+ if ! perf stat -C "0,1" -a true > "${stat_output}" 2>&1
+ then
+ echo "stat -C <cpu> test [Failed - command failed for cpus 0,1]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+ if ! grep -E -q "Performance counter stats for" "${stat_output}"
+ then
+ echo "stat -C <cpu> test [Failed - missing output for cpus 0,1]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ # Test range "0-1"
+ if ! perf stat -C "0-1" -a true > "${stat_output}" 2>&1
+ then
+ echo "stat -C <cpu> test [Failed - command failed for cpus 0-1]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+ if ! grep -E -q "Performance counter stats for" "${stat_output}"
+ then
+ echo "stat -C <cpu> test [Failed - missing output for cpus 0-1]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+ fi
+
+ echo "stat -C <cpu> test [Success]"
+}
+
+test_stat_no_aggr() {
+ echo "stat -A test"
+ if ! perf stat -A -a true > "${stat_output}" 2>&1
+ then
+ echo "stat -A test [Failed - command failed]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ if ! grep -E -q "CPU" "${stat_output}"
+ then
+ echo "stat -A test [Failed - missing CPU column]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+ echo "stat -A test [Success]"
+}
+
+test_stat_detailed() {
+ echo "stat -d test"
+ if ! perf stat -d true > "${stat_output}" 2>&1
+ then
+ echo "stat -d test [Failed - command failed]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ if ! grep -E -q "Performance counter stats" "${stat_output}"
+ then
+ echo "stat -d test [Failed - missing output]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ if ! perf stat -dd true > "${stat_output}" 2>&1
+ then
+ echo "stat -dd test [Failed - command failed]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ if ! grep -E -q "Performance counter stats" "${stat_output}"
+ then
+ echo "stat -dd test [Failed - missing output]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ if ! perf stat -ddd true > "${stat_output}" 2>&1
+ then
+ echo "stat -ddd test [Failed - command failed]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ if ! grep -E -q "Performance counter stats" "${stat_output}"
+ then
+ echo "stat -ddd test [Failed - missing output]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ echo "stat -d test [Success]"
+}
+
+test_stat_repeat() {
+ echo "stat -r test"
+ if ! perf stat -r 2 true > "${stat_output}" 2>&1
+ then
+ echo "stat -r test [Failed - command failed]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ if ! grep -E -q "\([[:space:]]*\+-.*%[[:space:]]*\)" "${stat_output}"
+ then
+ echo "stat -r test [Failed - missing variance]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+ echo "stat -r test [Success]"
+}
+
+test_stat_pid() {
+ echo "stat -p test"
+ sleep 1 &
+ pid=$!
+ if ! perf stat -p $pid > "${stat_output}" 2>&1
+ then
+ echo "stat -p test [Failed - command failed]"
+ cat "${stat_output}"
+ err=1
+ kill $pid 2>/dev/null || true
+ wait $pid 2>/dev/null || true
+ return
+ fi
+
+ if ! grep -E -q "Performance counter stats" "${stat_output}"
+ then
+ echo "stat -p test [Failed - missing output]"
+ cat "${stat_output}"
+ err=1
+ else
+ echo "stat -p test [Success]"
+ fi
+ kill $pid 2>/dev/null || true
+ wait $pid 2>/dev/null || true
+}
+
+test_stat_delay() {
+ echo "stat -D test"
+ if ! env LC_ALL=C perf stat -D 1000 -e duration_time sleep 2 > "${stat_output}" 2>&1
+ then
+ echo "stat -D test [Failed - command failed]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ duration=$(grep "duration_time" "${stat_output}" | awk '{print $1}' | tr -d ',')
+ elapsed=$(grep "seconds time elapsed" "${stat_output}" | awk '{print $1}')
+
+ if [ -z "$duration" ] || [ -z "$elapsed" ]
+ then
+ echo "stat -D test [Failed - failed to find duration_time or time elapsed in output]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+
+ # Compare duration (ns) and elapsed (s) using awk to handle float and allow tolerance.
+ if ! awk -v d="$duration" -v e="$elapsed" '
+ BEGIN {
+ diff = d - (e * 1e9);
+ if (diff < 0) diff = -diff;
+ # Allow 200ms tolerance (200,000,000 ns) for loaded CI machines.
+ if (diff > 200000000) {
+ printf "Fail: duration (%d ns) and elapsed (%f s) mismatch (diff %d ns)\n", d, e, diff;
+ exit 1;
+ }
+ # Lower bound check: must be at least 0.5s.
+ if (d < 500000000) {
+ printf "Fail: duration (%d ns) is abnormally small\n", d;
+ exit 1;
+ }
+ # Upper bound check: must be strictly less than 1.7s (proving delay was excluded).
+ if (d > 1700000000) {
+ printf "Fail: duration (%d ns) is too large (delay might not be excluded)\n", d;
+ exit 1;
+ }
+ exit 0;
+ }'
+ then
+ echo "stat -D test [Failed - validation failed]"
+ cat "${stat_output}"
+ err=1
+ return
+ fi
+ echo "stat -D test [Success]"
+}
+
+test_csv_json_fail() {
+ echo "stat -x <sep> -j test"
+ if perf stat -x , -j true > /dev/null 2>&1
+ then
+ echo "stat -x <sep> -j test [Failed - command should have errored]"
+ err=1
+ else
+ echo "stat -x <sep> -j test [Success]"
+ fi
+}
+
+test_hide_zero_events_stat() {
+ echo "Hide zero events stat test"
+ if ! perf stat -e context-switches,cpu-migrations true > "${stat_output}" 2>&1
+ then
+ echo "Hide zero events stat test [Skipped event parsing failed]"
+ return
+ fi
+
+ zero_event=""
+ if grep -q -E "[[:space:]]+0[[:space:]]+context-switches" "${stat_output}"; then
+ zero_event="context-switches"
+ elif grep -q -E "[[:space:]]+0[[:space:]]+cpu-migrations" "${stat_output}"; then
+ zero_event="cpu-migrations"
+ fi
+
+ if [ -z "$zero_event" ]; then
+ echo "Hide zero events stat test [Skipped - no zero count event found]"
+ return
+ fi
+
+ if ! perf stat --hide-zero-events -e context-switches,cpu-migrations true > "${stat_output}" 2>&1
+ then
+ echo "Hide zero events stat test [Failed - command failed]"
+ err=1
+ return
+ fi
+
+ if grep -q -E "$zero_event" "${stat_output}"
+ then
+ echo "Hide zero events stat test [Failed - zero event $zero_event was not hidden]"
+ err=1
+ return
+ fi
+
+ # Check that --metric-only works with --hide-zero-events
+ if ! perf stat --hide-zero-events --metric-only -e instructions,cycles true > "${stat_output}" 2>&1
+ then
+ echo "Hide zero events stat test [Failed - metric-only command failed]"
+ err=1
+ return
+ fi
+
+ echo "Hide zero events stat test [Success]"
+}
+
+test_default_stat
+test_null_stat
+test_offline_cpu_stat
+test_stat_record_report
+test_stat_record_script
+test_stat_repeat_weak_groups
+test_topdown_groups
+test_topdown_weak_groups
+test_cputype
+test_hybrid
+test_stat_cpu
+test_stat_no_aggr
+test_stat_detailed
+test_stat_repeat
+test_stat_pid
+test_stat_delay
+test_csv_json_fail
+test_hide_zero_events_stat
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/stat_all_metricgroups.sh b/tools/perf/tests/shell/stat_all_metricgroups.sh
new file mode 100755
index 000000000..81bc7070b
--- /dev/null
+++ b/tools/perf/tests/shell/stat_all_metricgroups.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+# perf all metricgroups test
+# SPDX-License-Identifier: GPL-2.0
+
+ParanoidAndNotRoot()
+{
+ [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
+}
+
+system_wide_flag="-a"
+if ParanoidAndNotRoot 0
+then
+ system_wide_flag=""
+fi
+
+err=3
+skip=0
+for m in $(perf list --raw-dump metricgroups)
+do
+ echo "Testing $m"
+ result=$(perf stat -M "$m" $system_wide_flag sleep 0.01 2>&1)
+ result_err=$?
+ if [[ $result_err -eq 0 ]]
+ then
+ if [[ "$err" -ne 1 ]]
+ then
+ err=0
+ fi
+ else
+ if [[ "$result" =~ \
+ "Access to performance monitoring and observability operations is limited" ]]
+ then
+ echo "Permission failure"
+ echo $result
+ skip=1
+ elif [[ "$result" =~ "in per-thread mode, enable system wide" ]]
+ then
+ echo "Permissions - need system wide mode"
+ echo $result
+ skip=1
+ elif [[ "$m" == @(Default2|Default3|Default4) ]]
+ then
+ echo "Ignoring failures in $m that may contain unsupported legacy events"
+ else
+ echo "Metric group $m failed"
+ echo $result
+ err=1 # Fail
+ fi
+ fi
+done
+
+if [[ "$err" -eq 3 && "$skip" -eq 1 ]]
+then
+ err=2
+fi
+
+exit $err
diff --git a/tools/perf/tests/shell/stat_all_metrics.sh b/tools/perf/tests/shell/stat_all_metrics.sh
new file mode 100755
index 000000000..df5fa27b2
--- /dev/null
+++ b/tools/perf/tests/shell/stat_all_metrics.sh
@@ -0,0 +1,148 @@
+#!/bin/bash
+# perf all metrics test
+# SPDX-License-Identifier: GPL-2.0
+
+ParanoidAndNotRoot()
+{
+ [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
+}
+
+test_prog="true"
+system_wide_flag="-a"
+if ParanoidAndNotRoot 0
+then
+ system_wide_flag=""
+ test_prog="perf test -w noploop 0.01"
+fi
+
+check_metric() {
+ local output="$1"
+ local status="$2"
+ local metric="$3"
+
+ if [[ $status -ne 0 || ! "$output" =~ ${metric:0:50} ]]; then
+ return 1
+ fi
+
+ if [[ "$output" =~ "<not counted>" || "$output" =~ "<not supported>" ]]; then
+ return 1
+ fi
+
+ return 0
+}
+
+skip=0
+err=3
+for m in $(perf list --raw-dump metrics); do
+ echo "Testing $m"
+ result=$(perf stat -M "$m" $system_wide_flag -- $test_prog 2>&1)
+ result_err=$?
+
+ if check_metric "$result" $result_err "$m"; then
+ if [[ "$err" -ne 1 ]]
+ then
+ err=0
+ fi
+ continue
+ fi
+
+ if [[ "$result" =~ "Access to performance monitoring and observability operations is limited" || \
+ "$result" =~ "in per-thread mode, enable system wide" || \
+ "$result" =~ "<not supported>" || \
+ "$result" =~ "Cannot resolve IDs for" || \
+ "$result" =~ "No supported events found" || \
+ "$result" =~ "FP_ARITH" || \
+ "$result" =~ "AMX" || \
+ "$result" =~ "PMM" ]]
+ then
+ true
+ else
+ result=$(perf stat -M "$m" $system_wide_flag -- perf test -w noploop 0.1 2>&1)
+ result_err=$?
+
+ if check_metric "$result" $result_err "$m"; then
+ if [[ "$err" -ne 1 ]]
+ then
+ err=0
+ fi
+ continue
+ fi
+ fi
+
+ # If retry also failed, determine if we skip, ignore, or fail
+ if [[ "$result" =~ "Access to performance monitoring and observability operations is limited" ]]
+ then
+ echo "[Skipped $m] Permission failure"
+ echo $result
+ if [[ $err -eq 0 ]]
+ then
+ skip=1
+ fi
+ continue
+ elif [[ "$result" =~ "in per-thread mode, enable system wide" ]]
+ then
+ echo "[Skipped $m] Permissions - need system wide mode"
+ echo $result
+ if [[ $err -eq 0 ]]
+ then
+ skip=1
+ fi
+ continue
+ elif [[ "$result" =~ "<not supported>" || \
+ "$result" =~ "Cannot resolve IDs for" || \
+ "$result" =~ "No supported events found" ]]
+ then
+ if [[ $(perf list --raw-dump $m) == "Default"* ]]
+ then
+ echo "[Ignored $m] failed but as a Default metric this can be expected"
+ echo $result
+ continue
+ fi
+ echo "[Skipped $m] Not supported events"
+ echo $result
+ if [[ $err -eq 0 ]]
+ then
+ skip=1
+ fi
+ continue
+ elif [[ "$result" =~ "<not counted>" ]]
+ then
+ echo "[Skipped $m] Not counted events"
+ echo $result
+ if [[ $err -eq 0 ]]
+ then
+ skip=1
+ fi
+ continue
+ elif [[ "$result" =~ "FP_ARITH" || "$result" =~ "AMX" ]]
+ then
+ echo "[Skipped $m] FP issues"
+ echo $result
+ if [[ $err -eq 0 ]]
+ then
+ skip=1
+ fi
+ continue
+ elif [[ "$result" =~ "PMM" ]]
+ then
+ echo "[Skipped $m] Optane memory issues"
+ echo $result
+ if [[ $err -eq 0 ]]
+ then
+ skip=1
+ fi
+ continue
+ fi
+
+ echo "[Failed $m] has non-zero error '$result_err' or not printed/counted in:"
+ echo "$result"
+ err=1
+done
+
+# return SKIP only if no success returned
+if [[ "$err" -eq 3 && "$skip" -eq 1 ]]
+then
+ err=2
+fi
+
+exit "$err"
diff --git a/tools/perf/tests/shell/stat_all_pfm.sh b/tools/perf/tests/shell/stat_all_pfm.sh
new file mode 100755
index 000000000..c08c186af
--- /dev/null
+++ b/tools/perf/tests/shell/stat_all_pfm.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+# perf all libpfm4 events test
+# SPDX-License-Identifier: GPL-2.0
+
+if perf version --build-options | grep HAVE_LIBPFM | grep -q OFF
+then
+ echo "Skipping, no libpfm4 support"
+ exit 2
+fi
+
+err=0
+for p in $(perf list --raw-dump pfm)
+do
+ if echo "$p" | grep -q unc_
+ then
+ echo "Skipping uncore event '$p' that may require additional options."
+ continue
+ fi
+ echo "Testing $p"
+ result=$(perf stat --pfm-events "$p" true 2>&1)
+ x=$?
+ if echo "$result" | grep -q "failed to parse event $p : invalid or missing unit mask"
+ then
+ continue
+ fi
+ if test "$x" -ne "0"
+ then
+ echo "Unexpected exit code '$x'"
+ err=1
+ fi
+ if ! echo "$result" | grep -q "$p" && ! echo "$result" | grep -q "<not supported>"
+ then
+ # We failed to see the event and it is supported. Possibly the workload was
+ # too small so retry with something longer.
+ result=$(perf stat --pfm-events "$p" perf bench internals synthesize 2>&1)
+ x=$?
+ if test "$x" -ne "0"
+ then
+ echo "Unexpected exit code '$x'"
+ err=1
+ fi
+ if ! echo "$result" | grep -q "$p"
+ then
+ echo "Event '$p' not printed in:"
+ echo "$result"
+ err=1
+ fi
+ fi
+done
+
+exit "$err"
diff --git a/tools/perf/tests/shell/stat_all_pmu.sh b/tools/perf/tests/shell/stat_all_pmu.sh
new file mode 100755
index 000000000..9c466c0ef
--- /dev/null
+++ b/tools/perf/tests/shell/stat_all_pmu.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+# perf all PMU test (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+err=0
+result=""
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ echo "$result"
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+# Test all PMU events; however exclude parameterized ones (name contains '?')
+for p in $(perf list --raw-dump pmu | sed 's/[[:graph:]]\+?[[:graph:]]\+[[:space:]]//g')
+do
+ echo -n "Testing $p -- "
+ output=$(perf stat -e "$p" true 2>&1)
+ stat_result=$?
+ if echo "$output" | grep -q "$p"
+ then
+ # Event seen in output.
+ if [ $stat_result -eq 0 ] && ! echo "$output" | grep -q "<not supported>"
+ then
+ # Event supported.
+ echo "supported"
+ continue
+ elif echo "$output" | grep -q "<not supported>"
+ then
+ # Event not supported, so ignore.
+ echo "not supported"
+ continue
+ elif echo "$output" | grep -q "No permission to enable"
+ then
+ # No permissions, so ignore.
+ echo "no permission to enable"
+ continue
+ elif echo "$output" | grep -q "Bad event name"
+ then
+ # Non-existent event.
+ echo "Error: Bad event name"
+ echo "$output"
+ err=1
+ continue
+ fi
+ fi
+
+ if echo "$output" | grep -q "Access to performance monitoring and observability operations is limited."
+ then
+ # Access is limited, so ignore.
+ echo "access limited"
+ continue
+ fi
+
+ # We failed to see the event and it is supported. Possibly the workload was
+ # too small so retry with something longer.
+ output=$(perf stat -e "$p" perf bench internals synthesize 2>&1)
+ if echo "$output" | grep -q "$p"
+ then
+ # Event seen in output.
+ echo "supported"
+ continue
+ fi
+ echo "Error: event '$p' not printed in:"
+ echo "$output"
+ err=1
+done
+
+trap - EXIT TERM INT
+exit $err
diff --git a/tools/perf/tests/shell/stat_bpf_counters.sh b/tools/perf/tests/shell/stat_bpf_counters.sh
new file mode 100755
index 000000000..11de77ee3
--- /dev/null
+++ b/tools/perf/tests/shell/stat_bpf_counters.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+# perf stat --bpf-counters test (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+# Get the first allowed CPU
+CPU=$(taskset -c -p $$ | awk -F': ' '{print $2}' | awk -F'[,-]' '{print $1}')
+if [ -z "$CPU" ]; then
+ CPU=0
+fi
+workload=(taskset -c "$CPU" awk 'BEGIN { for (i=0; i<10000000; i++) sum+=i }')
+
+# check whether $2 is within +/- 15% of $1
+compare_number()
+{
+ first_num=$1
+ second_num=$2
+
+ # upper bound is first_num * 115%
+ upper=$(expr $first_num + $first_num / 20 \* 3 )
+ # lower bound is first_num * 85%
+ lower=$(expr $first_num - $first_num / 20 \* 3 )
+
+ if [ $second_num -gt $upper ] || [ $second_num -lt $lower ]; then
+ echo "The difference between $first_num and $second_num are greater than 15%."
+ exit 1
+ fi
+}
+
+check_counts()
+{
+ base_instructions=$1
+ bpf_instructions=$2
+
+ if [ "$base_instructions" = "<not" ]; then
+ echo "Skipping: instructions event not counted"
+ exit 2
+ fi
+ if [ "$bpf_instructions" = "<not" ]; then
+ echo "Failed: instructions not counted with --bpf-counters"
+ exit 1
+ fi
+}
+
+test_bpf_counters()
+{
+ printf "Testing --bpf-counters "
+ base_instructions=$(perf stat --no-big-num -e instructions:u -- "${workload[@]}" 2>&1 | \
+ awk -v i=0 -v c=0 '/instructions/ { \
+ if ($1 != "<not") { i++; c += $1 } \
+ } END { if (i > 0) printf "%.0f", c; else print "<not" }')
+ bpf_instructions=$(perf stat --no-big-num --bpf-counters -e instructions:u \
+ -- "${workload[@]}" 2>&1 | \
+ awk -v i=0 -v c=0 '/instructions/ { \
+ if ($1 != "<not") { i++; c += $1 } \
+ } END { if (i > 0) printf "%.0f", c; else print "<not" }')
+ check_counts $base_instructions $bpf_instructions
+ compare_number $base_instructions $bpf_instructions
+ echo "[Success]"
+}
+
+test_bpf_modifier()
+{
+ printf "Testing bpf event modifier "
+ stat_output=$(perf stat --no-big-num \
+ -e instructions/name=base_instructions/u,instructions/name=bpf_instructions/bu \
+ -- "${workload[@]}" 2>&1)
+ base_instructions=$(echo "$stat_output"| \
+ awk -v i=0 -v c=0 '/base_instructions/ { \
+ if ($1 != "<not") { i++; c += $1 } \
+ } END { if (i > 0) printf "%.0f", c; else print "<not" }')
+ bpf_instructions=$(echo "$stat_output"| \
+ awk -v i=0 -v c=0 '/bpf_instructions/ { \
+ if ($1 != "<not") { i++; c += $1 } \
+ } END { if (i > 0) printf "%.0f", c; else print "<not" }')
+ check_counts $base_instructions $bpf_instructions
+ compare_number $base_instructions $bpf_instructions
+ echo "[Success]"
+}
+
+# skip if --bpf-counters is not supported
+if ! perf stat -e instructions --bpf-counters true > /dev/null 2>&1; then
+ if [ "$1" = "-v" ]; then
+ echo "Skipping: --bpf-counters not supported"
+ perf --no-pager stat -e instructions --bpf-counters true || true
+ fi
+ exit 2
+fi
+
+test_bpf_counters
+test_bpf_modifier
+
+exit 0
diff --git a/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh b/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh
new file mode 100755
index 000000000..febe3af06
--- /dev/null
+++ b/tools/perf/tests/shell/stat_bpf_counters_cgrp.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+# perf stat --bpf-counters --for-each-cgroup test
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+test_cgroups=
+if [ "$1" = "-v" ]; then
+ verbose="1"
+fi
+
+# skip if --bpf-counters --for-each-cgroup is not supported
+check_bpf_counter()
+{
+ if ! perf stat -a --bpf-counters --for-each-cgroup / true > /dev/null 2>&1; then
+ if [ "${verbose}" = "1" ]; then
+ echo "Skipping: --bpf-counters --for-each-cgroup not supported"
+ perf --no-pager stat -a --bpf-counters --for-each-cgroup / true || true
+ fi
+ exit 2
+ fi
+}
+
+# find two cgroups to measure
+find_cgroups()
+{
+ # try usual systemd slices first
+ if [ -d /sys/fs/cgroup/system.slice ] && [ -d /sys/fs/cgroup/user.slice ]; then
+ test_cgroups="system.slice,user.slice"
+ return
+ fi
+
+ # try root and self cgroups
+ find_cgroups_self_cgrp=$(grep perf_event /proc/self/cgroup | cut -d: -f3)
+ if [ -z ${find_cgroups_self_cgrp} ]; then
+ # cgroup v2 doesn't specify perf_event
+ find_cgroups_self_cgrp=$(grep ^0: /proc/self/cgroup | cut -d: -f3)
+ fi
+
+ if [ -z ${find_cgroups_self_cgrp} ]; then
+ test_cgroups="/"
+ else
+ test_cgroups="/,${find_cgroups_self_cgrp}"
+ fi
+}
+
+# As cgroup events are cpu-wide, we cannot simply compare the result.
+# Just check if it runs without failure and has non-zero results.
+check_system_wide_counted()
+{
+ check_system_wide_counted_output=$(perf stat -a --bpf-counters --for-each-cgroup ${test_cgroups} -e cpu-clock -x, sleep 1 2>&1)
+ if echo ${check_system_wide_counted_output} | grep -q -F "<not "; then
+ echo "Some system-wide events are not counted"
+ if [ "${verbose}" = "1" ]; then
+ echo ${check_system_wide_counted_output}
+ fi
+ exit 1
+ fi
+}
+
+# Missing flags in evlist__clone() resulted in different output.
+# Just check the number of output lines for simple verification.
+check_evlist_expand()
+{
+ normal_output=$(perf stat -a -x, true 2>&1 | wc -l)
+ expand_output=$(perf stat -a -x, --bpf-counters --for-each-cgroup / true 2>&1 | wc -l)
+ if [ "${normal_output}" != "${expand_output}" ]; then
+ if [ "${verbose}" = "1" ]; then
+ echo "Normal output has ${normal_output} lines, but it now has ${expand_output}"
+ fi
+ exit 1
+ fi
+}
+
+check_bpf_counter
+find_cgroups
+
+check_system_wide_counted
+check_evlist_expand
+
+exit 0
diff --git a/tools/perf/tests/shell/stat_metrics_cgrp.sh b/tools/perf/tests/shell/stat_metrics_cgrp.sh
new file mode 100755
index 000000000..d4226ee0a
--- /dev/null
+++ b/tools/perf/tests/shell/stat_metrics_cgrp.sh
@@ -0,0 +1,200 @@
+#!/bin/bash
+# perf stat metrics --for-each-cgroup test
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+test_cgroups=
+
+log_verbose() {
+ echo "$1"
+}
+
+is_numeric_and_non_zero() {
+ local val="$1"
+ if [[ "${val}" =~ ^[0-9]+$ ]] && [ "${val}" -gt 0 ]
+ then
+ return 0 # True
+ fi
+ return 1 # False
+}
+
+# skip if system-wide is not supported
+check_system_wide()
+{
+ log_verbose "Checking system-wide..."
+ if ! perf stat -a --metrics=insn_per_cycle sleep 0.01 > /dev/null 2>&1
+ then
+ log_verbose "Skipping: system-wide monitoring not supported"
+ exit 2
+ fi
+}
+
+# find two cgroups to measure
+find_cgroups()
+{
+ log_verbose "Finding cgroups..."
+ # try usual systemd slices first
+ if [ -d /sys/fs/cgroup/system.slice ] && [ -d /sys/fs/cgroup/user.slice ]
+ then
+ test_cgroups="system.slice,user.slice"
+ log_verbose "Found cgroups: ${test_cgroups}"
+ return
+ fi
+
+ # try root and self cgroups
+ find_cgroups_self_cgrp=$(grep perf_event /proc/self/cgroup | cut -d: -f3)
+ if [ -z "${find_cgroups_self_cgrp}" ]
+ then
+ # cgroup v2 doesn't specify perf_event
+ find_cgroups_self_cgrp=$(grep ^0: /proc/self/cgroup | cut -d: -f3)
+ fi
+
+ if [ -z "${find_cgroups_self_cgrp}" ]
+ then
+ test_cgroups="/"
+ else
+ test_cgroups="/,${find_cgroups_self_cgrp}"
+ fi
+ log_verbose "Found cgroups: ${test_cgroups}"
+}
+
+# Check if metric is reported for each cgroup
+# $1: extra options (e.g. --bpf-counters)
+check_metric_reported()
+{
+ local opts="$1"
+ local output
+
+ log_verbose "Running check_metric_reported with opts '${opts}'..."
+ # Run perf stat
+ if ! output=$(perf stat -a ${opts} \
+ --metrics=insn_per_cycle \
+ --for-each-cgroup "${test_cgroups}" \
+ -x, sleep 0.1 2>&1)
+ then
+ echo "FAIL: perf stat failed with exit code $?"
+ echo "Output: ${output}"
+ exit 1
+ fi
+
+ log_verbose "perf stat output:"
+ log_verbose "${output}"
+
+ # Split test_cgroups by comma
+ IFS=',' read -r -a cgrps <<< "${test_cgroups}"
+
+ for cgrp in "${cgrps[@]}"; do
+ # Find metric lines for this cgroup
+ # We use exact cgroup match with surrounding commas
+ local cgrp_lines
+ cgrp_lines=$(echo "${output}" | grep -F ",${cgrp}," | grep "insn_per_cycle" || true)
+
+ if [ -z "${cgrp_lines}" ]
+ then
+ echo "FAIL: No metric lines found for cgroup '${cgrp}'"
+ exit 1
+ fi
+
+ # Parse each metric line
+ while read -r line; do
+ [ -z "${line}" ] && continue
+
+ local val1
+ val1=$(echo "${line}" | cut -d, -f1)
+
+ local event_name
+ event_name=$(echo "${line}" | cut -d, -f3)
+
+ local cycles_val=""
+ local inst_val=""
+
+ if echo "${event_name}" | grep -q -i "cycles"
+ then
+ cycles_val="${val1}"
+ # Find corresponding instructions event
+ local inst_event_name
+ inst_event_name="${event_name/cpu-cycles/instructions}"
+ inst_event_name="${inst_event_name/cycles/instructions}"
+
+ local inst_line
+ inst_line=$(echo "${output}" | \
+ grep -F ",${cgrp}," | \
+ grep -F "${inst_event_name}" || true)
+ inst_val=$(echo "${inst_line}" | cut -d, -f1)
+ elif echo "${event_name}" | grep -q -i "instructions"
+ then
+ inst_val="${val1}"
+ # Find corresponding cycles event (try cpu-cycles
+ # first, then cycles)
+ local cycles_event_name
+ cycles_event_name="${event_name/instructions/cpu-cycles}"
+ local cycles_line
+ cycles_line=$(echo "${output}" | \
+ grep -F ",${cgrp}," | \
+ grep -F "${cycles_event_name}" || true)
+
+ if [ -z "${cycles_line}" ]
+ then
+ # Try "cycles" instead of "cpu-cycles"
+ cycles_event_name="${event_name/instructions/cycles}"
+ cycles_line=$(echo "${output}" | \
+ grep -F ",${cgrp}," | \
+ grep -F "${cycles_event_name}" || true)
+ fi
+ cycles_val=$(echo "${cycles_line}" | cut -d, -f1)
+ fi
+
+ log_verbose "Cgroup '${cgrp}': event '${event_name}' \
+val '${cycles_val}', inst val '${inst_val}'"
+
+ # Only enforce metric check if both cycles and
+ # instructions have non-zero numeric counts
+ if is_numeric_and_non_zero "${cycles_val}" && \
+ is_numeric_and_non_zero "${inst_val}"
+ then
+ log_verbose "Enforcing metric check for cgroup '${cgrp}' \
+event '${event_name}'"
+ # Check for nan or nested in the metric value (7th field)
+ # Actually we can just check the whole line for simplicity
+ if echo "${line}" | grep -q -i -E ",nan,|,nested,"
+ then
+ echo "FAIL: Invalid metric value (nan/nested) \
+for cgroup '${cgrp}'"
+ echo "Line: ${line}"
+ exit 1
+ fi
+ # Check for empty metric value (2 consecutive
+ # commas before the unit)
+ if echo "${line}" | grep -q -E ",,[[:space:]]*[^,]*insn_per_cycle"
+ then
+ echo "FAIL: Empty metric value for cgroup '${cgrp}'"
+ echo "Line: ${line}"
+ exit 1
+ fi
+ else
+ log_verbose "Skipping metric check for cgroup '${cgrp}' \
+event '${event_name}' (idle or not counted)"
+ fi
+ done <<< "${cgrp_lines}"
+ done
+ log_verbose "check_metric_reported passed for opts '${opts}'"
+}
+
+check_system_wide
+find_cgroups
+
+# Test 1: Without BPF counters
+check_metric_reported ""
+
+# Test 2: With BPF counters (if supported)
+log_verbose "Checking BPF support..."
+if perf stat -a --bpf-counters --for-each-cgroup / true > /dev/null 2>&1
+then
+ log_verbose "BPF supported, running Test 2..."
+ check_metric_reported "--bpf-counters"
+else
+ log_verbose "BPF not supported, skipping Test 2"
+fi
+
+exit 0
diff --git a/tools/perf/tests/shell/stat_metrics_values.sh b/tools/perf/tests/shell/stat_metrics_values.sh
new file mode 100755
index 000000000..86c5c7f70
--- /dev/null
+++ b/tools/perf/tests/shell/stat_metrics_values.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# perf metrics value validation
+# SPDX-License-Identifier: GPL-2.0
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/setup_python.sh
+. "${shelldir}"/lib/setup_python.sh
+
+grep -q GenuineIntel /proc/cpuinfo || { echo Skipping non-Intel; exit 2; }
+
+# Skip if no permission to record system-wide events
+if ! perf stat -a -e instructions sleep 0.01 >/dev/null 2>&1; then
+ echo "Skipping: no permission to record system-wide events (-a)"
+ exit 2
+fi
+
+
+pythonvalidator=$(dirname $0)/lib/perf_metric_validation.py
+rulefile=$(dirname $0)/lib/perf_metric_validation_rules.json
+tmpdir=$(mktemp -d /tmp/__perf_test.program.XXXXX)
+workload="perf bench futex hash -r 1 -s"
+
+# Add -debug, save data file and full rule file
+echo "Launch python validation script $pythonvalidator"
+echo "Output will be stored in: $tmpdir"
+for cputype in /sys/bus/event_source/devices/cpu_*; do
+ cputype=$(basename "$cputype")
+ echo "Testing metrics for: $cputype"
+ $PYTHON $pythonvalidator -rule $rulefile -output_dir $tmpdir -wl "${workload}" \
+ -cputype "${cputype}"
+ ret=$?
+ rm -rf $tmpdir
+ if [ $ret -ne 0 ]; then
+ echo "Metric validation return with errors. Please check metrics reported with errors."
+ fi
+done
+exit $ret
+
diff --git a/tools/perf/tests/shell/test_arm_callgraph_fp.sh b/tools/perf/tests/shell/test_arm_callgraph_fp.sh
new file mode 100755
index 000000000..9172dd68a
--- /dev/null
+++ b/tools/perf/tests/shell/test_arm_callgraph_fp.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+# Check Arm64 callgraphs are complete in fp mode
+# SPDX-License-Identifier: GPL-2.0
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+if [ "$(uname -m)" != "aarch64" ]; then
+ exit 2
+fi
+
+if perf version --build-options | grep HAVE_DWARF_UNWIND_SUPPORT | grep -q OFF
+then
+ echo "Skipping, no dwarf unwind support"
+ exit 2
+fi
+
+skip_test_missing_symbol leafloop
+
+PERF_DATA=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+TEST_PROGRAM="perf test -w leafloop"
+
+cleanup_files()
+{
+ rm -f "$PERF_DATA"
+}
+
+trap cleanup_files EXIT TERM INT
+
+# shellcheck disable=SC2086
+perf record -o "$PERF_DATA" --call-graph fp -e cycles//u --user-callchains -- $TEST_PROGRAM
+
+# Try opening the file so any immediate errors are visible in the log
+perf script -i "$PERF_DATA" -F comm,ip,sym | head -n4
+
+# expected perf-script output if 'leaf' has been inserted correctly:
+#
+# perf
+# 728 leaf
+# 753 parent
+# 76c leafloop
+# ... remaining stack to main() ...
+
+# Each frame is separated by a tab, some spaces and an address
+SEP="[[:space:]]+ [[:xdigit:]]+"
+perf script -i "$PERF_DATA" -F comm,ip,sym | tr '\n' ' ' | \
+ grep -E -q "perf $SEP leaf $SEP parent $SEP leafloop"
diff --git a/tools/perf/tests/shell/test_arm_spe.sh b/tools/perf/tests/shell/test_arm_spe.sh
new file mode 100755
index 000000000..bb76ea88a
--- /dev/null
+++ b/tools/perf/tests/shell/test_arm_spe.sh
@@ -0,0 +1,143 @@
+#!/bin/bash
+# Check Arm SPE trace data recording and synthesized samples (exclusive)
+
+# Uses the 'perf record' to record trace data of Arm SPE events;
+# then verify if any SPE event samples are generated by SPE with
+# 'perf script' and 'perf report' commands.
+
+# SPDX-License-Identifier: GPL-2.0
+# German Gomez <german.gomez@arm.com>, 2021
+
+skip_if_no_arm_spe_event() {
+ perf list pmu | grep -E -q 'arm_spe_[0-9]+//' && return 0
+
+ # arm_spe event doesn't exist
+ return 2
+}
+
+skip_if_no_arm_spe_event || exit 2
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+glb_err=0
+
+cleanup_files()
+{
+ rm -f ${perfdata}
+ rm -f ${perfdata}.old
+ exit $glb_err
+}
+
+trap cleanup_files EXIT TERM INT
+
+arm_spe_report() {
+ if [ $2 = 0 ]; then
+ echo "$1: PASS"
+ elif [ $2 = 2 ]; then
+ echo "$1: SKIPPED"
+ else
+ echo "$1: FAIL"
+ glb_err=$2
+ fi
+}
+
+perf_script_samples() {
+ echo "Looking at perf.data file for dumping samples:"
+
+ # from arm-spe.c/arm_spe_synth_events()
+ events="(ld1-miss|ld1-access|llc-miss|lld-access|tlb-miss|tlb-access|branch-miss|remote-access|memory)"
+
+ # Below is an example of the samples dumping:
+ # dd 3048 [002] 1 l1d-access: ffffaa64999c __GI___libc_write+0x3c (/lib/aarch64-linux-gnu/libc-2.27.so)
+ # dd 3048 [002] 1 tlb-access: ffffaa64999c __GI___libc_write+0x3c (/lib/aarch64-linux-gnu/libc-2.27.so)
+ # dd 3048 [002] 1 memory: ffffaa64999c __GI___libc_write+0x3c (/lib/aarch64-linux-gnu/libc-2.27.so)
+ perf script -F,-time -i ${perfdata} 2>&1 | \
+ grep -E " +$1 +[0-9]+ .* +${events}:(.*:)? +" > /dev/null 2>&1
+}
+
+perf_report_samples() {
+ echo "Looking at perf.data file for reporting samples:"
+
+ # Below is an example of the samples reporting:
+ # 73.04% 73.04% dd libc-2.27.so [.] _dl_addr
+ # 7.71% 7.71% dd libc-2.27.so [.] getenv
+ # 2.59% 2.59% dd ld-2.27.so [.] strcmp
+ perf report --stdio -i ${perfdata} 2>&1 | \
+ grep -E " +[0-9]+\.[0-9]+% +[0-9]+\.[0-9]+% +$1 " > /dev/null 2>&1
+}
+
+arm_spe_snapshot_test() {
+ echo "Recording trace with snapshot mode $perfdata"
+ perf record -o ${perfdata} -e arm_spe// -S \
+ -- dd if=/dev/zero of=/dev/null > /dev/null 2>&1 &
+ PERFPID=$!
+
+ # Wait for perf program
+ sleep 1
+
+ # Send signal to snapshot trace data
+ kill -USR2 $PERFPID
+
+ # Stop perf program
+ kill $PERFPID
+ wait $PERFPID
+
+ perf_script_samples dd &&
+ perf_report_samples dd
+
+ err=$?
+ arm_spe_report "SPE snapshot testing" $err
+}
+
+arm_spe_system_wide_test() {
+ echo "Recording trace with system-wide mode $perfdata"
+
+ perf record -o - -e dummy -a -B true > /dev/null 2>&1
+ if [ $? != 0 ]; then
+ arm_spe_report "SPE system-wide testing" 2
+ return
+ fi
+
+ perf record -o ${perfdata} -e arm_spe// -a --no-bpf-event \
+ -- dd if=/dev/zero of=/dev/null count=100000 > /dev/null 2>&1
+
+ perf_script_samples dd &&
+ perf_report_samples dd
+
+ err=$?
+ arm_spe_report "SPE system-wide testing" $err
+}
+
+arm_spe_discard_test() {
+ echo "SPE discard mode"
+
+ for f in /sys/bus/event_source/devices/arm_spe_*; do
+ if [ -e "$f/format/discard" ]; then
+ cpu=$(cut -c -1 "$f/cpumask")
+ break
+ fi
+ done
+
+ if [ -z $cpu ]; then
+ arm_spe_report "SPE discard mode not present" 2
+ return
+ fi
+
+ # Test can use wildcard SPE instance and Perf will only open the event
+ # on instances that have that format flag. But make sure the target
+ # runs on an instance with discard mode otherwise we're not testing
+ # anything.
+ perf record -o ${perfdata} -e arm_spe/discard/ -N -B --no-bpf-event \
+ -- taskset --cpu-list $cpu true
+
+ if perf report -i ${perfdata} --stats | grep 'AUX events\|AUXTRACE events'; then
+ arm_spe_report "SPE discard mode found unexpected data" 1
+ else
+ arm_spe_report "SPE discard mode" 0
+ fi
+}
+
+arm_spe_snapshot_test
+arm_spe_system_wide_test
+arm_spe_discard_test
+
+exit $glb_err
diff --git a/tools/perf/tests/shell/test_arm_spe_fork.sh b/tools/perf/tests/shell/test_arm_spe_fork.sh
new file mode 100755
index 000000000..5bcca51c0
--- /dev/null
+++ b/tools/perf/tests/shell/test_arm_spe_fork.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+# Check Arm SPE doesn't hang when there are forks
+
+# SPDX-License-Identifier: GPL-2.0
+# German Gomez <german.gomez@arm.com>, 2022
+
+skip_if_no_arm_spe_event() {
+ perf list pmu | grep -E -q 'arm_spe_[0-9]+//' && return 0
+ return 2
+}
+
+skip_if_no_arm_spe_event || exit 2
+
+TEST_PROGRAM="perf test -w sqrtloop 10"
+PERF_DATA=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+PERF_RECORD_LOG=$(mktemp /tmp/__perf_test.log.XXXXX)
+
+cleanup_files()
+{
+ echo "Cleaning up files..."
+ rm -f ${PERF_RECORD_LOG}
+ rm -f ${PERF_DATA}
+}
+
+trap cleanup_files EXIT TERM INT
+
+echo "Recording workload..."
+perf record -o ${PERF_DATA} -e arm_spe/period=65536/ -vvv -- $TEST_PROGRAM > ${PERF_RECORD_LOG} 2>&1 &
+PERFPID=$!
+
+# Check if perf hangs by checking the perf-record logs.
+sleep 1
+log0=$(wc -l $PERF_RECORD_LOG)
+echo Log lines = $log0
+sleep 1
+log1=$(wc -l $PERF_RECORD_LOG)
+echo Log lines after 1 second = $log1
+
+kill $PERFPID
+wait $PERFPID
+
+if [ "$log0" = "$log1" ];
+then
+ echo "SPE hang test: FAIL"
+ exit 1
+else
+ echo "SPE hang test: PASS"
+fi
+
+exit 0
diff --git a/tools/perf/tests/shell/test_bpf_metadata.sh b/tools/perf/tests/shell/test_bpf_metadata.sh
new file mode 100755
index 000000000..be67d56e0
--- /dev/null
+++ b/tools/perf/tests/shell/test_bpf_metadata.sh
@@ -0,0 +1,76 @@
+#!/bin/bash
+# BPF metadata collection test
+#
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${perfdata}".old
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_bpf_metadata() {
+ echo "Checking BPF metadata collection"
+
+ if ! perf check -q feature libbpf-strings ; then
+ echo "Basic BPF metadata test [skipping - not supported]"
+ err=0
+ return
+ fi
+
+ # This is a basic invocation of perf record
+ # that invokes the perf_sample_filter BPF program.
+ if ! perf record -e task-clock --filter 'ip > 0' \
+ -o "${perfdata}" sleep 1 2> /dev/null
+ then
+ echo "Basic BPF metadata test [Failed record]"
+ err=1
+ return
+ fi
+
+ # The BPF programs that ship with "perf" all have the following
+ # variable defined at compile time:
+ #
+ # const char bpf_metadata_perf_version[] SEC(".rodata") = <...>;
+ #
+ # This invocation looks for a PERF_RECORD_BPF_METADATA event,
+ # and checks that its content contains the string given by
+ # "perf version".
+ VERS=$(perf version | awk '{print $NF}')
+ if ! perf script --show-bpf-events -i "${perfdata}" | awk '
+ /PERF_RECORD_BPF_METADATA.*perf_sample_filter/ {
+ header = 1;
+ }
+ /^ *entry/ {
+ if (header) { header = 0; entry = 1; }
+ }
+ $0 !~ /^ *entry/ {
+ entry = 0;
+ }
+ /perf_version/ {
+ if (entry) print $NF;
+ }
+ ' | grep -qF "$VERS"
+ then
+ echo "Basic BPF metadata test [Failed invalid output]"
+ err=1
+ return
+ fi
+ echo "Basic BPF metadata test [Success]"
+}
+
+test_bpf_metadata
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/test_brstack.sh b/tools/perf/tests/shell/test_brstack.sh
new file mode 100755
index 000000000..71550e0b3
--- /dev/null
+++ b/tools/perf/tests/shell/test_brstack.sh
@@ -0,0 +1,273 @@
+#!/bin/bash
+# Check branch stack sampling
+
+# SPDX-License-Identifier: GPL-2.0
+# German Gomez <german.gomez@arm.com>, 2022
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+# skip the test if the hardware doesn't support branch stack sampling
+# and if the architecture doesn't support filter types: any,save_type,u
+if ! perf record -o- --no-buildid --branch-filter any,save_type,u -- true > /dev/null 2>&1 ; then
+ echo "skip: system doesn't support filter types: any,save_type,u"
+ exit 2
+fi
+
+skip_test_missing_symbol brstack_bench
+
+err=0
+TMPDIR=$(mktemp -d /tmp/__perf_test.program.XXXXX)
+TESTPROG="perf test -w brstack"
+
+cleanup() {
+ rm -rf $TMPDIR
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ set +e
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+is_arm64() {
+ [ "$(uname -m)" = "aarch64" ];
+}
+
+has_kaslr_bug() {
+ [ "$(uname -m)" != "aarch64" ];
+}
+
+check_branches() {
+ if ! tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep -E -m1 -q "$1"; then
+ echo "ERROR: Branches missing $1"
+ err=1
+ fi
+}
+
+test_user_branches() {
+ echo "Testing user branch stack sampling"
+
+ start_err=$err
+ err=0
+ perf record -o "$TMPDIR/perf.data" --branch-filter any,save_type,u -- ${TESTPROG} > "$TMPDIR/record.txt" 2>&1
+ perf script -i "$TMPDIR/perf.data" --fields brstacksym > "$TMPDIR/perf.script"
+
+ # example of branch entries:
+ # brstack_foo+0x14/brstack_bar+0x40/P/-/-/0/CALL
+
+ expected=(
+ "^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/IND_CALL/.*$"
+ "^brstack_foo\+[^ ]*/brstack_bar\+[^ ]*/CALL/.*$"
+ "^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/CALL/.*$"
+ "^brstack_bench\+[^ ]*/brstack_bar\+[^ ]*/CALL/.*$"
+ "^brstack_bar\+[^ ]*/brstack_foo\+[^ ]*/RET/.*$"
+ "^brstack_foo\+[^ ]*/brstack_bench\+[^ ]*/RET/.*$"
+ "^brstack_bench\+[^ ]*/brstack_bench\+[^ ]*/COND/.*$"
+ "^brstack\+[^ ]*/brstack\+[^ ]*/UNCOND/.*$"
+ )
+ for x in "${expected[@]}"
+ do
+ check_branches "$x"
+ done
+
+ # Dump addresses only this time
+ perf script -i "$TMPDIR/perf.data" --fields brstack | \
+ tr ' ' '\n' > "$TMPDIR/perf.script"
+
+ # There should be no kernel addresses in the target with the u option.
+ local regex="0x[89a-f][0-9a-f]{15}"
+ if has_kaslr_bug; then
+ # If the system has a kaslr bug that may leak kernel addresses
+ # in the source of something like an ERET/SYSRET. Make the regex
+ # more specific and just check the target address is in user
+ # code.
+ regex="^0x[0-9a-f]{0,16}/0x[89a-f][0-9a-f]{15}/"
+ fi
+ if grep -q -E -m1 "$regex" $TMPDIR/perf.script; then
+ echo "Testing user branch stack sampling [Failed kernel address found in user mode]"
+ err=1
+ fi
+ # some branch types are still not being tested:
+ # IND COND_CALL COND_RET SYSRET SERROR NO_TX
+ if [ $err -eq 0 ]; then
+ echo "Testing user branch stack sampling [Passed]"
+ err=$start_err
+ else
+ echo "Testing user branch stack sampling [Failed]"
+ fi
+}
+
+test_trap_eret_branches() {
+ echo "Testing trap & eret branches"
+
+ if ! is_arm64; then
+ echo "Testing trap & eret branches [Skipped not arm64]"
+ return
+ fi
+ start_err=$err
+ local ret=1
+ for loops in 1000 10000 100000; do
+ err=0
+ perf record -o $TMPDIR/perf.data --branch-filter any,save_type,u,k -- \
+ perf test -w traploop $loops > "$TMPDIR/record.txt" 2>&1
+ perf script -i $TMPDIR/perf.data --fields brstacksym | \
+ tr ' ' '\n' > $TMPDIR/perf.script
+
+ # BRBINF<n>.TYPE == TRAP are mapped to PERF_BR_IRQ by the BRBE driver
+ check_branches "^trap_bench\+[^ ]+/[^ ]/IRQ/"
+ check_branches "^[^ ]+/trap_bench\+[^ ]+/ERET/"
+ if [ $err -eq 0 ]; then
+ ret=0
+ break
+ fi
+ done
+
+ if [ $ret -eq 0 ]; then
+ echo "Testing trap & eret branches [Passed]"
+ err=$start_err
+ else
+ echo "Testing trap & eret branches [Failed]"
+ err=1
+ fi
+}
+
+test_kernel_branches() {
+ echo "Testing kernel branch sampling"
+
+ if ! perf record --branch-filter any,k -o- -- true > "$TMPDIR/record.txt" 2>&1; then
+ echo "Testing that k option [Skipped not enough privileges]"
+ return
+ fi
+ start_err=$err
+ local ret=1
+ for loops in 1000 10000 100000; do
+ err=0
+ perf record -o $TMPDIR/perf.data --branch-filter any,k -- \
+ perf bench syscall basic --loop $loops > "$TMPDIR/record.txt" 2>&1
+ perf script -i $TMPDIR/perf.data --fields brstack | \
+ tr ' ' '\n' > $TMPDIR/perf.script
+
+ # Example of branch entries:
+ # "0xffffffff93bda241/0xffffffff93bda20f/M/-/-/..."
+ # Source addresses come first in user or kernel code. Next is the target
+ # address that must be in the kernel.
+
+ # Look for source addresses with top bit set
+ if ! grep -q -E -m1 "^0x[89a-f][0-9a-f]{15}" $TMPDIR/perf.script; then
+ err=1
+ fi
+ # Look for no target addresses without top bit set
+ if grep -q -E -m1 "^0x[0-9a-f]{0,16}/0x[0-7][0-9a-f]{1,15}/" \
+ $TMPDIR/perf.script; then
+ err=1
+ fi
+ if [ $err -eq 0 ]; then
+ ret=0
+ break
+ fi
+ done
+
+ if [ $ret -eq 0 ]; then
+ echo "Testing kernel branch sampling [Passed]"
+ err=$start_err
+ else
+ echo "Testing kernel branch sampling [Failed]"
+ err=1
+ fi
+}
+
+# first argument <arg0> is the argument passed to "--branch-stack <arg0>,save_type,u"
+# second argument are the expected branch types for the given filter
+test_filter() {
+ test_filter_filter=$1
+ test_filter_expect=$2
+
+ echo "Testing branch stack filtering permutation ($test_filter_filter,$test_filter_expect)"
+ perf record -o "$TMPDIR/perf.data" --branch-filter "$test_filter_filter,save_type,u" -- \
+ ${TESTPROG} > "$TMPDIR/record.txt" 2>&1
+ perf script -i "$TMPDIR/perf.data" --fields brstack > "$TMPDIR/perf.script"
+
+ # fail if we find any branch type that doesn't match any of the expected ones
+ # also consider UNKNOWN branch types (-)
+ if [ ! -s "$TMPDIR/perf.script" ]
+ then
+ echo "Testing branch stack filtering [Failed empty script output]"
+ err=1
+ return
+ fi
+ # Look for lines not matching test_filter_expect ignoring issues caused
+ # by empty output
+ tr -s ' ' '\n' < "$TMPDIR/perf.script" | grep '.' | \
+ grep -E -vm1 "^[^ ]*/($test_filter_expect|-|( *))/.*$" \
+ > "$TMPDIR/perf.script-filtered" || true
+ if [ -s "$TMPDIR/perf.script-filtered" ]
+ then
+ echo "Testing branch stack filtering [Failed unexpected branch filter]"
+ cat "$TMPDIR/perf.script"
+ err=1
+ return
+ fi
+ echo "Testing branch stack filtering [Passed]"
+}
+
+test_syscall() {
+ echo "Testing syscalls"
+ # skip if perf doesn't have enough privileges
+ if ! perf record --branch-filter any,k -o- -- true > "$TMPDIR/record.txt" 2>&1; then
+ echo "Testing syscalls [Skipped: not enough privileges]"
+ return
+ fi
+ start_err=$err
+ local ret=1
+ for loops in 8000 30000 100000; do
+ err=0
+ perf record -o $TMPDIR/perf.data --branch-filter \
+ any_call,save_type,u,k -c 10007 -- \
+ perf bench syscall basic --loop $loops > "$TMPDIR/record.txt" 2>&1
+ perf script -i $TMPDIR/perf.data --fields brstacksym | \
+ tr ' ' '\n' > $TMPDIR/perf.script
+
+ check_branches "getppid[^ ]*/SYSCALL/"
+ if [ $err -eq 0 ]; then
+ ret=0
+ break
+ fi
+ done
+
+ if [ $ret -eq 0 ]; then
+ echo "Testing syscalls [Passed]"
+ err=$start_err
+ else
+ echo "Testing syscalls [Failed]"
+ err=1
+ fi
+}
+set -e
+
+test_user_branches
+test_syscall
+test_kernel_branches
+test_trap_eret_branches
+
+any_call="CALL|IND_CALL|COND_CALL|SYSCALL|IRQ"
+
+if is_arm64; then
+ any_call="$any_call|FAULT_DATA|FAULT_INST"
+fi
+
+test_filter "any_call" "$any_call"
+test_filter "call" "CALL|SYSCALL"
+test_filter "cond" "COND"
+test_filter "any_ret" "RET|COND_RET|SYSRET|ERET"
+
+test_filter "call,cond" "CALL|SYSCALL|COND"
+test_filter "any_call,cond" "$any_call|COND"
+test_filter "any_call,cond,any_ret" "$any_call|COND|RET|COND_RET"
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/test_data_symbol.sh b/tools/perf/tests/shell/test_data_symbol.sh
new file mode 100755
index 000000000..d61b5659a
--- /dev/null
+++ b/tools/perf/tests/shell/test_data_symbol.sh
@@ -0,0 +1,91 @@
+#!/bin/bash
+# Test data symbol (exclusive)
+
+# SPDX-License-Identifier: GPL-2.0
+# Leo Yan <leo.yan@linaro.org>, 2022
+
+shelldir=$(dirname "$0")
+
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+skip_if_no_mem_event() {
+ perf mem record -e list 2>&1 | grep -E -q 'available' && return 0
+ return 2
+}
+
+skip_if_no_mem_event || exit 2
+
+skip_test_missing_symbol workload_datasym_buf1
+
+TEST_PROGRAM="perf test -w datasym"
+PERF_DATA=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+ERR_FILE=$(mktemp /tmp/__perf_test.stderr.XXXXX)
+
+check_result() {
+ # The memory report format is as below:
+ # 99.92% ... [.] workload_datasym_buf1+0x38
+ result=$(perf mem report -i ${PERF_DATA} -s symbol_daddr -q 2>&1 |
+ awk '/workload_datasym_buf1/ { print $4 }')
+
+ # Testing is failed if has no any sample for "workload_datasym_buf1"
+ [ -z "$result" ] && return 1
+
+ while IFS= read -r line; do
+ # The "data1" and "data2" fields in structure
+ # "workload_datasym_buf1" have offset "0x0" and "0x38", returns
+ # failure if detect any other offset value.
+ if [ "$line" != "workload_datasym_buf1+0x0" ] && \
+ [ "$line" != "workload_datasym_buf1+0x38" ]; then
+ return 1
+ fi
+ done <<< "$result"
+
+ return 0
+}
+
+cleanup_files()
+{
+ echo "Cleaning up files..."
+ rm -f ${PERF_DATA}
+}
+
+trap cleanup_files exit term int
+
+echo "Recording workload..."
+
+is_amd=$(grep -E -c 'vendor_id.*AuthenticAMD' /proc/cpuinfo)
+if (($is_amd >= 1)); then
+ mem_events="$(perf mem record -v -e list 2>&1)"
+ if ! [[ "$mem_events" =~ ^mem\-ldst.*ibs_op/(.*)/.*available ]]; then
+ echo "ERROR: mem-ldst event is not matching"
+ exit 1
+ fi
+
+ # --ldlat on AMD:
+ # o Zen4 and earlier uarch does not support ldlat
+ # o Even on supported platforms, it's disabled (--ldlat=0) by default.
+ ldlat=${BASH_REMATCH[1]}
+ if [[ -n $ldlat ]]; then
+ if ! [[ "$ldlat" =~ ldlat=0 ]]; then
+ echo "ERROR: ldlat not initialized to 0?"
+ exit 1
+ fi
+
+ mem_events="$(perf mem record -v --ldlat=150 -e list 2>&1)"
+ if ! [[ "$mem_events" =~ ^mem-ldst.*ibs_op/ldlat=150/.*available ]]; then
+ echo "ERROR: --ldlat not honored?"
+ exit 1
+ fi
+ fi
+
+ # perf mem/c2c internally uses IBS PMU on AMD CPU which doesn't
+ # support user/kernel filtering and per-process monitoring on older
+ # kernels, spin program on specific CPU and test in per-CPU mode.
+ perf mem record -vvv -o ${PERF_DATA} -C 0 -- taskset -c 0 $TEST_PROGRAM 2>"${ERR_FILE}"
+else
+ perf mem record -vvv --all-user -o ${PERF_DATA} -- $TEST_PROGRAM 2>"${ERR_FILE}"
+fi
+
+check_result
+exit $?
diff --git a/tools/perf/tests/shell/test_dwarf_unwind_multithreaded.sh b/tools/perf/tests/shell/test_dwarf_unwind_multithreaded.sh
new file mode 100755
index 000000000..49e6e3af7
--- /dev/null
+++ b/tools/perf/tests/shell/test_dwarf_unwind_multithreaded.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# Test libdw unwinding of multi-threaded processes (exclusive)
+
+set -e
+
+if ! perf check feature -q libdw-dwarf-unwind; then
+ echo "Skip: libdw DWARF unwinding is not available"
+ exit 2
+fi
+
+tmpdir=$(mktemp -d /tmp/perf-test-dwarf-unwind-multithreaded.XXXXXX)
+perf_data="$tmpdir/perf.data"
+perf_script="$tmpdir/perf-script.txt"
+nr_threads=4
+nr_worker_threads=$((nr_threads - 1))
+
+cleanup()
+{
+ trap - EXIT TERM INT
+ rm -rf "$tmpdir"
+}
+
+trap cleanup EXIT TERM INT
+
+if ! perf record -q -e task-clock:u -F 99 --call-graph dwarf,8192 \
+ -o "$perf_data" -- perf test -w thloop 2 "$nr_threads"
+then
+ echo "Skip: failed to record task-clock:u"
+ exit 2
+fi
+
+if ! perf script --unwind-style=libdw \
+ -F comm,pid,tid,event,ip,sym -i "$perf_data" > "$perf_script"
+then
+ echo "Error: failed to process the recording with libdw" >&2
+ exit 1
+fi
+
+nr_unwound_threads=$(
+ awk '
+ BEGIN { RS = "" }
+
+ # thfunc is the worker-only caller of test_loop. Finding it proves
+ # that libdw unwound beyond the sampled leaf for this worker TID.
+ /thfunc/ {
+ split($2, id, "/")
+ seen[id[2]] = 1
+ }
+
+ END {
+ for (tid in seen)
+ nr_tids++
+ print nr_tids + 0
+ }
+ ' "$perf_script"
+)
+
+if [ "$nr_unwound_threads" -ne "$nr_worker_threads" ]; then
+ echo "Error: expected callchains for $nr_worker_threads worker TIDs," \
+ "found $nr_unwound_threads" >&2
+ exit 1
+fi
+
+exit 0
diff --git a/tools/perf/tests/shell/test_event_open_fallback.sh b/tools/perf/tests/shell/test_event_open_fallback.sh
new file mode 100755
index 000000000..9420a7557
--- /dev/null
+++ b/tools/perf/tests/shell/test_event_open_fallback.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+# Perf event open fallback test
+# SPDX-License-Identifier: GPL-2.0
+
+skip_cnt=0
+ok_cnt=0
+err_cnt=0
+
+perf_record()
+{
+ perf record -o /dev/null "$@" -- true 1>/dev/null 2>&1
+}
+
+test_decrease_precise_ip()
+{
+ echo "Decrease precise ip test"
+
+ perf list pmu | grep -q 'cycles' || return 2
+
+ if ! perf_record -e cycles; then
+ return 2
+ fi
+
+ # It should reduce precision level down to 0 if needed.
+ if ! perf_record -e cycles:P; then
+ return 1
+ fi
+ return 0
+}
+
+test_decrease_precise_ip_complicated()
+{
+ echo "Decrease precise ip test (complicated case)"
+
+ perf list pmu | grep -q 'mem-loads-aux' || return 2
+
+ if ! perf_record -e '{mem-loads-aux:S,mem-loads:PS}'; then
+ return 1
+ fi
+ return 0
+}
+
+count_result()
+{
+ if [ "$1" -eq 2 ] ; then
+ skip_cnt=$((skip_cnt + 1))
+ return
+ fi
+ if [ "$1" -eq 0 ] ; then
+ ok_cnt=$((ok_cnt + 1))
+ return
+ fi
+ err_cnt=$((err_cnt + 1))
+}
+
+ret=0
+test_decrease_precise_ip || ret=$? ; count_result $ret ; ret=0
+test_decrease_precise_ip_complicated || ret=$? ; count_result $ret ; ret=0
+
+cleanup
+
+if [ ${err_cnt} -gt 0 ] ; then
+ exit 1
+fi
+
+if [ ${ok_cnt} -gt 0 ] ; then
+ exit 0
+fi
+
+# Skip
+exit 2
diff --git a/tools/perf/tests/shell/test_intel_pt.sh b/tools/perf/tests/shell/test_intel_pt.sh
new file mode 100755
index 000000000..26243ff76
--- /dev/null
+++ b/tools/perf/tests/shell/test_intel_pt.sh
@@ -0,0 +1,558 @@
+#!/bin/bash
+# Miscellaneous Intel PT testing (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+# Skip if no Intel PT
+perf list pmu | grep -q 'intel_pt//' || exit 2
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/waiting.sh
+. "${shelldir}"/lib/waiting.sh
+
+skip_cnt=0
+ok_cnt=0
+err_cnt=0
+
+temp_dir=$(mktemp -d /tmp/perf-test-intel-pt-sh.XXXXXXXXXX)
+
+tmpfile="${temp_dir}/tmp-perf.data"
+perfdatafile="${temp_dir}/test-perf.data"
+outfile="${temp_dir}/test-out.txt"
+errfile="${temp_dir}/test-err.txt"
+awkscript="${temp_dir}/awkscript"
+maxbrstack="${temp_dir}/maxbrstack.py"
+
+cleanup()
+{
+ trap - EXIT TERM INT
+ sane=$(echo "${temp_dir}" | cut -b 1-26)
+ if [ "${sane}" = "/tmp/perf-test-intel-pt-sh" ] ; then
+ echo "--- Cleaning up ---"
+ rm -f "${temp_dir}/"*
+ rmdir "${temp_dir}"
+ fi
+}
+
+trap_cleanup()
+{
+ cleanup
+ exit 1
+}
+
+trap trap_cleanup EXIT TERM INT
+
+# perf record for testing without decoding
+perf_record_no_decode()
+{
+ # Options to speed up recording: no post-processing, no build-id cache update,
+ # and no BPF events.
+ perf record -B -N --no-bpf-event "$@"
+}
+
+# perf record for testing should not need BPF events
+perf_record_no_bpf()
+{
+ # Options for no BPF events
+ perf record --no-bpf-event "$@"
+}
+
+can_cpu_wide()
+{
+ echo "Checking for CPU-wide recording on CPU $1"
+ if ! perf_record_no_decode -o "${tmpfile}" -e dummy:u -C "$1" true >/dev/null 2>&1 ; then
+ echo "No so skipping"
+ return 2
+ fi
+ echo OK
+ return 0
+}
+
+test_system_wide_side_band()
+{
+ echo "--- Test system-wide sideband ---"
+
+ # Need CPU 0 and CPU 1
+ can_cpu_wide 0 || return $?
+ can_cpu_wide 1 || return $?
+
+ # Record on CPU 0 a task running on CPU 1
+ perf_record_no_decode -o "${perfdatafile}" -e intel_pt//u -C 0 -- taskset --cpu-list 1 uname
+
+ # Should get MMAP events from CPU 1 because they can be needed to decode
+ mmap_cnt=$(perf script -i "${perfdatafile}" --no-itrace --show-mmap-events -C 1 2>/dev/null | grep -c MMAP)
+
+ if [ "${mmap_cnt}" -gt 0 ] ; then
+ echo OK
+ return 0
+ fi
+
+ echo "Failed to record MMAP events on CPU 1 when tracing CPU 0"
+ return 1
+}
+
+can_kernel()
+{
+ if [ -z "${can_kernel_trace}" ] ; then
+ can_kernel_trace=0
+ perf_record_no_decode -o "${tmpfile}" -e dummy:k true >/dev/null 2>&1 && can_kernel_trace=1
+ fi
+ if [ ${can_kernel_trace} -eq 0 ] ; then
+ echo "SKIP: no kernel tracing"
+ return 2
+ fi
+ return 0
+}
+
+test_per_thread()
+{
+ k="$1"
+ desc="$2"
+
+ echo "--- Test per-thread ${desc}recording ---"
+
+ if [ "${k}" = "k" ] ; then
+ can_kernel || return 2
+ fi
+
+ cat <<- "_end_of_file_" > "${awkscript}"
+ BEGIN {
+ s = "[ ]*"
+ u = s"[0-9]+"s
+ d = s"[0-9-]+"s
+ x = s"[0-9a-fA-FxX]+"s
+ mmapping = "idx"u": mmapping fd"u
+ set_output = "idx"u": set output fd"u"->"u
+ perf_event_open = "sys_perf_event_open: pid"d"cpu"d"group_fd"d"flags"x"="u
+ }
+
+ /perf record opening and mmapping events/ {
+ if (!done)
+ active = 1
+ }
+
+ /perf record done opening and mmapping events/ {
+ active = 0
+ done = 1
+ }
+
+ $0 ~ perf_event_open && active {
+ match($0, perf_event_open)
+ $0 = substr($0, RSTART, RLENGTH)
+ pid = $3
+ cpu = $5
+ fd = $11
+ print "pid " pid " cpu " cpu " fd " fd " : " $0
+ fd_array[fd] = fd
+ pid_array[fd] = pid
+ cpu_array[fd] = cpu
+ }
+
+ $0 ~ mmapping && active {
+ match($0, mmapping)
+ $0 = substr($0, RSTART, RLENGTH)
+ fd = $5
+ print "fd " fd " : " $0
+ if (fd in fd_array) {
+ mmap_array[fd] = 1
+ } else {
+ print "Unknown fd " fd
+ exit 1
+ }
+ }
+
+ $0 ~ set_output && active {
+ match($0, set_output)
+ $0 = substr($0, RSTART, RLENGTH)
+ fd = $6
+ fd_to = $8
+ print "fd " fd " fd_to " fd_to " : " $0
+ if (fd in fd_array) {
+ if (fd_to in fd_array) {
+ set_output_array[fd] = fd_to
+ } else {
+ print "Unknown fd " fd_to
+ exit 1
+ }
+ } else {
+ print "Unknown fd " fd
+ exit 1
+ }
+ }
+
+ END {
+ print "Checking " length(fd_array) " fds"
+ for (fd in fd_array) {
+ if (fd in mmap_array) {
+ pid = pid_array[fd]
+ if (pid != -1) {
+ if (pid in pids) {
+ print "More than 1 mmap for PID " pid
+ exit 1
+ }
+ pids[pid] = 1
+ }
+ cpu = cpu_array[fd]
+ if (cpu != -1) {
+ if (cpu in cpus) {
+ print "More than 1 mmap for CPU " cpu
+ exit 1
+ }
+ cpus[cpu] = 1
+ }
+ } else if (!(fd in set_output_array)) {
+ print "No mmap for fd " fd
+ exit 1
+ }
+ }
+ n = length(pids)
+ if (n != thread_cnt) {
+ print "Expected " thread_cnt " per-thread mmaps - found " n
+ exit 1
+ }
+ }
+ _end_of_file_
+
+ perf test -w thloop 30 2 &
+ w1=$!
+ perf test -w thloop 30 2 &
+ w2=$!
+ echo "Workload PIDs are $w1 and $w2"
+ wait_for_threads ${w1} 2
+ wait_for_threads ${w2} 2
+
+ perf_record_no_decode -o "${perfdatafile}" -e intel_pt//u"${k}" -vvv --per-thread -p "${w1},${w2}" 2>"${errfile}" >"${outfile}" &
+ ppid=$!
+ echo "perf PID is $ppid"
+ wait_for_perf_to_start ${ppid} "${errfile}" || return 1
+
+ kill ${w1}
+ wait_for_process_to_exit ${w1} || return 1
+ is_running ${ppid} || return 1
+
+ kill ${w2}
+ wait_for_process_to_exit ${w2} || return 1
+ wait_for_process_to_exit ${ppid} || return 1
+
+ awk -v thread_cnt=4 -f "${awkscript}" "${errfile}" || return 1
+
+ echo OK
+ return 0
+}
+
+test_jitdump()
+{
+ echo "--- Test tracing self-modifying code that uses jitdump ---"
+
+ if ! perf check feature -q libelf ; then
+ echo "SKIP: libelf is needed for jitdump"
+ return 2
+ fi
+
+ # Change to temp_dir so jitdump collateral files go there
+ cd "${temp_dir}"
+ perf_record_no_bpf -o "${tmpfile}" -e intel_pt//u perf test -w jitdump
+ perf inject -i "${tmpfile}" -o "${perfdatafile}" --jit
+ decode_br_cnt=$(perf script -i "${perfdatafile}" --itrace=b | wc -l)
+ # Note that overflow and lost errors are suppressed for the error count
+ decode_err_cnt=$(perf script -i "${perfdatafile}" --itrace=e-o-l | grep -ci error)
+ cd -
+ # Should be thousands of branches
+ if [ "${decode_br_cnt}" -lt 1000 ] ; then
+ echo "Decode failed, only ${decode_br_cnt} branches"
+ return 1
+ fi
+ # Should be no errors
+ if [ "${decode_err_cnt}" -ne 0 ] ; then
+ echo "Decode failed, ${decode_err_cnt} errors"
+ perf script -i "${perfdatafile}" --itrace=e-o-l --show-mmap-events | cat
+ return 1
+ fi
+
+ echo OK
+ return 0
+}
+
+test_packet_filter()
+{
+ echo "--- Test with MTC and TSC disabled ---"
+ # Disable MTC and TSC
+ perf_record_no_decode -o "${perfdatafile}" -e intel_pt/mtc=0,tsc=0/u uname
+ # Should not get MTC packet
+ mtc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "MTC 0x")
+ if [ "${mtc_cnt}" -ne 0 ] ; then
+ echo "Failed to filter with mtc=0"
+ return 1
+ fi
+ # Should not get TSC package
+ tsc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "TSC 0x")
+ if [ "${tsc_cnt}" -ne 0 ] ; then
+ echo "Failed to filter with tsc=0"
+ return 1
+ fi
+ echo OK
+ return 0
+}
+
+test_disable_branch()
+{
+ echo "--- Test with branches disabled ---"
+ # Disable branch
+ perf_record_no_decode -o "${perfdatafile}" -e intel_pt/branch=0/u uname
+ # Should not get branch related packets
+ tnt_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "TNT 0x")
+ tip_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "TIP 0x")
+ fup_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "FUP 0x")
+ if [ "${tnt_cnt}" -ne 0 ] || [ "${tip_cnt}" -ne 0 ] || [ "${fup_cnt}" -ne 0 ] ; then
+ echo "Failed to disable branches"
+ return 1
+ fi
+ echo OK
+ return 0
+}
+
+test_time_cyc()
+{
+ echo "--- Test with/without CYC ---"
+ # Check if CYC is supported
+ cyc=$(cat /sys/bus/event_source/devices/intel_pt/caps/psb_cyc)
+ if [ "${cyc}" != "1" ] ; then
+ echo "SKIP: CYC is not supported"
+ return 2
+ fi
+ # Enable CYC
+ perf_record_no_decode -o "${perfdatafile}" -e intel_pt/cyc/u uname
+ # should get CYC packets
+ cyc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "CYC 0x")
+ if [ "${cyc_cnt}" = "0" ] ; then
+ echo "Failed to get CYC packet"
+ return 1
+ fi
+ # Without CYC
+ perf_record_no_decode -o "${perfdatafile}" -e intel_pt//u uname
+ # Should not get CYC packets
+ cyc_cnt=$(perf script -i "${perfdatafile}" -D 2>/dev/null | grep -c "CYC 0x")
+ if [ "${cyc_cnt}" -gt 0 ] ; then
+ echo "Still get CYC packet without cyc"
+ return 1
+ fi
+ echo OK
+ return 0
+}
+
+test_sample()
+{
+ echo "--- Test recording with sample mode ---"
+ # Check if recording with sample mode is working
+ if ! perf_record_no_decode -o "${perfdatafile}" --aux-sample=8192 -e '{intel_pt//u,branch-misses:u}' uname ; then
+ echo "perf record failed with --aux-sample"
+ return 1
+ fi
+ # Check with event with PMU name
+ if perf_record_no_decode -o "${perfdatafile}" -e br_misp_retired.all_branches:u uname ; then
+ if ! perf_record_no_decode -o "${perfdatafile}" -e '{intel_pt//,br_misp_retired.all_branches/aux-sample-size=8192/}:u' uname ; then
+ echo "perf record failed with --aux-sample-size"
+ return 1
+ fi
+ fi
+ echo OK
+ return 0
+}
+
+test_kernel_trace()
+{
+ echo "--- Test with kernel trace ---"
+ # Check if recording with kernel trace is working
+ can_kernel || return 2
+ if ! perf_record_no_decode -o "${perfdatafile}" -e intel_pt//k -m1,128 uname ; then
+ echo "perf record failed with intel_pt//k"
+ return 1
+ fi
+ echo OK
+ return 0
+}
+
+test_virtual_lbr()
+{
+ echo "--- Test virtual LBR ---"
+ # Check if python script is supported
+ libpython=$(perf version --build-options | grep python | grep -cv OFF)
+ if [ "${libpython}" != "1" ] ; then
+ echo "SKIP: python scripting is not supported"
+ return 2
+ fi
+
+ # Python script to determine the maximum size of branch stacks
+ cat << "_end_of_file_" > "${maxbrstack}"
+from __future__ import print_function
+
+bmax = 0
+
+def process_event(param_dict):
+ if "brstack" in param_dict:
+ brstack = param_dict["brstack"]
+ n = len(brstack)
+ global bmax
+ if n > bmax:
+ bmax = n
+
+def trace_end():
+ print("max brstack", bmax)
+_end_of_file_
+
+ # Check if virtual lbr is working
+ perf_record_no_bpf -o "${perfdatafile}" --aux-sample -e '{intel_pt//,cycles}:u' uname
+ times_val=$(perf script -i "${perfdatafile}" --itrace=L -s "${maxbrstack}" 2>/dev/null | grep "max brstack " | cut -d " " -f 3)
+ case "${times_val}" in
+ [0-9]*) ;;
+ *) times_val=0;;
+ esac
+ if [ "${times_val}" -lt 2 ] ; then
+ echo "Failed with virtual lbr"
+ return 1
+ fi
+ echo OK
+ return 0
+}
+
+test_power_event()
+{
+ echo "--- Test power events ---"
+ # Check if power events are supported
+ power_event=$(cat /sys/bus/event_source/devices/intel_pt/caps/power_event_trace)
+ if [ "${power_event}" != "1" ] ; then
+ echo "SKIP: power_event_trace is not supported"
+ return 2
+ fi
+ if ! perf_record_no_decode -o "${perfdatafile}" -a -e intel_pt/pwr_evt/u uname ; then
+ echo "perf record failed with pwr_evt"
+ return 1
+ fi
+ echo OK
+ return 0
+}
+
+test_no_tnt()
+{
+ echo "--- Test with TNT packets disabled ---"
+ # Check if TNT disable is supported
+ notnt=$(cat /sys/bus/event_source/devices/intel_pt/caps/tnt_disable)
+ if [ "${notnt}" != "1" ] ; then
+ echo "SKIP: tnt_disable is not supported"
+ return 2
+ fi
+ perf_record_no_decode -o "${perfdatafile}" -e intel_pt/notnt/u uname
+ # Should be no TNT packets
+ tnt_cnt=$(perf script -i "${perfdatafile}" -D | grep -c TNT)
+ if [ "${tnt_cnt}" -ne 0 ] ; then
+ echo "TNT packets still there after notnt"
+ return 1
+ fi
+ echo OK
+ return 0
+}
+
+test_event_trace()
+{
+ echo "--- Test with event_trace ---"
+ # Check if event_trace is supported
+ event_trace=$(cat /sys/bus/event_source/devices/intel_pt/caps/event_trace)
+ if [ "${event_trace}" != 1 ] ; then
+ echo "SKIP: event_trace is not supported"
+ return 2
+ fi
+ if ! perf_record_no_decode -o "${perfdatafile}" -e intel_pt/event/u uname ; then
+ echo "perf record failed with event trace"
+ return 1
+ fi
+ echo OK
+ return 0
+}
+
+test_pipe()
+{
+ echo "--- Test with pipe mode ---"
+ # Check if it works with pipe
+ if ! perf_record_no_bpf -o- -e intel_pt//u uname | perf report -q -i- --itrace=i10000 ; then
+ echo "perf record + report failed with pipe mode"
+ return 1
+ fi
+ if ! perf_record_no_bpf -o- -e intel_pt//u uname | perf inject -b > /dev/null ; then
+ echo "perf record + inject failed with pipe mode"
+ return 1
+ fi
+ echo OK
+ return 0
+}
+
+test_pause_resume()
+{
+ echo "--- Test with pause / resume ---"
+ if ! perf_record_no_decode -o "${perfdatafile}" -e intel_pt/aux-action=start-paused/u uname ; then
+ echo "SKIP: pause / resume is not supported"
+ return 2
+ fi
+ if ! perf_record_no_bpf -o "${perfdatafile}" \
+ -e intel_pt/aux-action=start-paused/u \
+ -e instructions/period=50000,aux-action=resume,name=Resume/u \
+ -e instructions/period=100000,aux-action=pause,name=Pause/u uname ; then
+ echo "perf record with pause / resume failed"
+ return 1
+ fi
+ if ! perf script -i "${perfdatafile}" --itrace=b -Fperiod,event | \
+ awk 'BEGIN {paused=1;branches=0}
+ /Resume/ {paused=0}
+ /branches/ {if (paused) exit 1;branches=1}
+ /Pause/ {paused=1}
+ END {if (!branches) exit 1}' ; then
+ echo "perf record with pause / resume failed"
+ return 1
+ fi
+ echo OK
+ return 0
+}
+
+count_result()
+{
+ if [ "$1" -eq 2 ] ; then
+ skip_cnt=$((skip_cnt + 1))
+ return
+ fi
+ if [ "$1" -eq 0 ] ; then
+ ok_cnt=$((ok_cnt + 1))
+ return
+ fi
+ err_cnt=$((err_cnt + 1))
+}
+
+ret=0
+test_system_wide_side_band || ret=$? ; count_result $ret ; ret=0
+test_per_thread "" "" || ret=$? ; count_result $ret ; ret=0
+test_per_thread "k" "(incl. kernel) " || ret=$? ; count_result $ret ; ret=0
+test_jitdump || ret=$? ; count_result $ret ; ret=0
+test_packet_filter || ret=$? ; count_result $ret ; ret=0
+test_disable_branch || ret=$? ; count_result $ret ; ret=0
+test_time_cyc || ret=$? ; count_result $ret ; ret=0
+test_sample || ret=$? ; count_result $ret ; ret=0
+test_kernel_trace || ret=$? ; count_result $ret ; ret=0
+test_virtual_lbr || ret=$? ; count_result $ret ; ret=0
+test_power_event || ret=$? ; count_result $ret ; ret=0
+test_no_tnt || ret=$? ; count_result $ret ; ret=0
+test_event_trace || ret=$? ; count_result $ret ; ret=0
+test_pipe || ret=$? ; count_result $ret ; ret=0
+test_pause_resume || ret=$? ; count_result $ret ; ret=0
+
+cleanup
+
+echo "--- Done ---"
+
+if [ ${err_cnt} -gt 0 ] ; then
+ exit 1
+fi
+
+if [ ${ok_cnt} -gt 0 ] ; then
+ exit 0
+fi
+
+exit 2
diff --git a/tools/perf/tests/shell/test_java_symbol.sh b/tools/perf/tests/shell/test_java_symbol.sh
new file mode 100755
index 000000000..63a2cc9bf
--- /dev/null
+++ b/tools/perf/tests/shell/test_java_symbol.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+# Test java symbol
+
+# SPDX-License-Identifier: GPL-2.0
+# Leo Yan <leo.yan@linaro.org>, 2022
+
+# skip if there's no jshell
+if ! [ -x "$(command -v jshell)" ]; then
+ echo "skip: no jshell, install JDK"
+ exit 2
+fi
+
+PERF_DATA=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+PERF_INJ_DATA=$(mktemp /tmp/__perf_test.perf.data.inj.XXXXX)
+
+cleanup_files()
+{
+ echo "Cleaning up files..."
+ rm -f ${PERF_DATA}
+ rm -f ${PERF_INJ_DATA}
+}
+
+trap cleanup_files exit term int
+
+PERF_DIR=$(dirname "$(which perf)")
+if [ -e "$PWD/tools/perf/libperf-jvmti.so" ]; then
+ LIBJVMTI=$PWD/tools/perf/libperf-jvmti.so
+elif [ -e "$PWD/libperf-jvmti.so" ]; then
+ LIBJVMTI=$PWD/libperf-jvmti.so
+elif [ -e "$PERF_DIR/libperf-jvmti.so" ]; then
+ LIBJVMTI=$PERF_DIR/libperf-jvmti.so
+elif [ -e "$PREFIX/lib64/libperf-jvmti.so" ]; then
+ LIBJVMTI=$PREFIX/lib64/libperf-jvmti.so
+elif [ -e "$PREFIX/lib/libperf-jvmti.so" ]; then
+ LIBJVMTI=$PREFIX/lib/libperf-jvmti.so
+elif [ -e "/usr/lib/linux-tools-$(uname -a | awk '{ print $3 }' | sed -r 's/-generic//')/libperf-jvmti.so" ]; then
+ LIBJVMTI=/usr/lib/linux-tools-$(uname -a | awk '{ print $3 }' | sed -r 's/-generic//')/libperf-jvmti.so
+else
+ echo "Fail to find libperf-jvmti.so"
+
+ # JVMTI is a build option, skip the test if fail to find lib
+ exit 2
+fi
+
+cat <<EOF | perf record -k 1 -o $PERF_DATA jshell -s -J-agentpath:$LIBJVMTI
+int fib(int x) {
+ return x > 1 ? fib(x - 2) + fib(x - 1) : 1;
+}
+
+int q = 0;
+
+for (int i = 0; i < 10; i++)
+ q += fib(i);
+
+System.out.println(q);
+EOF
+
+if [ $? -ne 0 ]; then
+ echo "Fail to record for java program"
+ exit 1
+fi
+
+if ! DEBUGINFOD_URLS='' perf inject -i $PERF_DATA -o $PERF_INJ_DATA -j; then
+ echo "Fail to inject samples"
+ exit 1
+fi
+
+# Below is an example of the instruction samples reporting:
+# 8.18% jshell jitted-50116-29.so [.] Interpreter
+# 0.75% Thread-1 jitted-83602-1670.so [.] jdk.internal.jimage.BasicImageReader.getString(int)
+perf report --stdio -i ${PERF_INJ_DATA} 2>&1 | \
+ grep -E " +[0-9]+\.[0-9]+% .* (Interpreter|jdk\.internal).*" > /dev/null 2>&1
+
+if [ $? -ne 0 ]; then
+ echo "Fail to find java symbols"
+ exit 1
+fi
+
+exit 0
diff --git a/tools/perf/tests/shell/test_perf_data_converter_ctf.sh b/tools/perf/tests/shell/test_perf_data_converter_ctf.sh
new file mode 100755
index 000000000..bc58a8dab
--- /dev/null
+++ b/tools/perf/tests/shell/test_perf_data_converter_ctf.sh
@@ -0,0 +1,104 @@
+#!/bin/bash
+# 'perf data convert --to-ctf' command test
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+ctf_dir=$(mktemp -d /tmp/__perf_test.ctf.XXXXX)
+
+cleanup()
+{
+ rm -f "${perfdata}"
+ rm -rf "${ctf_dir}"
+ trap - exit term int
+}
+
+trap_cleanup()
+{
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit ${err}
+}
+trap trap_cleanup exit term int
+
+check_babeltrace2_support()
+{
+ if ! perf check feature babeltrace2-ctf-writer
+ then
+ echo "perf not linked with babeltrace2-ctf-writer, skipping test"
+ exit 2
+ fi
+}
+
+test_ctf_converter_file()
+{
+ echo "Testing Perf Data Conversion Command to CTF (File input)"
+ # Record some data
+ if ! perf record -o "$perfdata" -F 99 -g -- perf test -w noploop
+ then
+ echo "Failed to record perf data"
+ err=1
+ return
+ fi
+
+ # Cleanup previous ctf dir
+ rm -rf "${ctf_dir}"
+
+ # Convert
+ if ! perf data convert --to-ctf "$ctf_dir" --force -i "$perfdata"
+ then
+ echo "Perf Data Converter Command to CTF (File input) [FAILED]"
+ err=1
+ return
+ fi
+
+ if [ -d "${ctf_dir}" ] && [ "$(ls -A "${ctf_dir}")" ]
+ then
+ echo "Perf Data Converter Command to CTF (File input) [SUCCESS]"
+ else
+ echo "Perf Data Converter Command to CTF (File input) [FAILED]"
+ echo " Output directory empty or missing"
+ err=1
+ fi
+}
+
+test_ctf_converter_pipe()
+{
+ echo "Testing Perf Data Conversion Command to CTF (Pipe mode)"
+
+ # Cleanup previous ctf dir
+ rm -rf "${ctf_dir}"
+
+ # Record to stdout and pipe to $perfdata file
+ if ! perf record -o - -F 99 -g -- perf test -w noploop > "$perfdata"
+ then
+ echo "Failed to record perf data"
+ err=1
+ return
+ fi
+
+ if ! perf data convert --to-ctf "$ctf_dir" --force -i "$perfdata"
+ then
+ echo "Perf Data Converter Command to CTF (Pipe mode) [FAILED]"
+ err=1
+ return
+ fi
+
+ if [ -d "${ctf_dir}" ] && [ "$(ls -A "${ctf_dir}")" ]
+ then
+ echo "Perf Data Converter Command to CTF (Pipe mode) [SUCCESS]"
+ else
+ echo "Perf Data Converter Command to CTF (Pipe mode) [FAILED]"
+ echo " Output directory empty or missing"
+ err=1
+ fi
+}
+
+check_babeltrace2_support
+test_ctf_converter_file
+test_ctf_converter_pipe
+
+exit ${err}
diff --git a/tools/perf/tests/shell/test_perf_data_converter_json.sh b/tools/perf/tests/shell/test_perf_data_converter_json.sh
new file mode 100755
index 000000000..35d81e39a
--- /dev/null
+++ b/tools/perf/tests/shell/test_perf_data_converter_json.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+# 'perf data convert --to-json' command test
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/setup_python.sh
+. "${shelldir}"/lib/setup_python.sh
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+result=$(mktemp /tmp/__perf_test.output.json.XXXXX)
+
+cleanup()
+{
+ rm -f "${perfdata}*"
+ rm -f "${result}"
+ trap - exit term int
+}
+
+trap_cleanup()
+{
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup exit term int
+
+test_json_converter_command()
+{
+ echo "Testing Perf Data Conversion Command to JSON"
+ perf record -o "$perfdata" -F 99 -g -- perf test -w noploop
+ perf data convert --to-json "$result" --force -i "$perfdata"
+ if [ "$(cat ${result} | wc -l)" -gt "0" ] ; then
+ echo "Perf Data Converter Command to JSON [SUCCESS]"
+ else
+ echo "Perf Data Converter Command to JSON [FAILED]"
+ err=1
+ fi
+}
+
+test_json_converter_pipe()
+{
+ echo "Testing Perf Data Conversion Command to JSON (Pipe mode)"
+ perf record -o - -F 99 -g -- perf test -w noploop > "$perfdata"
+ cat "$perfdata" | perf data convert --to-json "$result" --force -i -
+ if [ "$(cat ${result} | wc -l)" -gt "0" ] ; then
+ echo "Perf Data Converter Command to JSON (Pipe mode) [SUCCESS]"
+ else
+ echo "Perf Data Converter Command to JSON (Pipe mode) [FAILED]"
+ err=1
+ fi
+}
+
+validate_json_format()
+{
+ echo "Validating Perf Data Converted JSON file"
+ if [ -f "$result" ] ; then
+ if $PYTHON -c "import json; json.load(open('$result'))" >/dev/null 2>&1 ; then
+ echo "The file contains valid JSON format [SUCCESS]"
+ else
+ echo "The file does not contain valid JSON format [FAILED]"
+ err=1
+ fi
+ else
+ echo "File not found [FAILED]"
+ err=1
+ fi
+}
+
+test_json_converter_command
+validate_json_format
+
+test_json_converter_pipe
+validate_json_format
+
+cleanup
+exit ${err}
diff --git a/tools/perf/tests/shell/test_stat_intel_tpebs.sh b/tools/perf/tests/shell/test_stat_intel_tpebs.sh
new file mode 100755
index 000000000..a330ecdb7
--- /dev/null
+++ b/tools/perf/tests/shell/test_stat_intel_tpebs.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+# test Intel TPEBS counting mode (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+ParanoidAndNotRoot() {
+ [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
+}
+
+if ! grep -q GenuineIntel /proc/cpuinfo
+then
+ echo "Skipping non-Intel"
+ exit 2
+fi
+
+if ParanoidAndNotRoot 0
+then
+ echo "Skipping paranoid >0 and not root"
+ exit 2
+fi
+
+stat_output=$(mktemp /tmp/__perf_stat_tpebs_output.XXXXX)
+
+cleanup() {
+ rm -rf "${stat_output}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cat "${stat_output}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+# Event to be used in tests
+event=cache-misses
+
+if ! perf record -e "${event}:p" -a -o /dev/null sleep 0.01 > "${stat_output}" 2>&1
+then
+ echo "Missing ${event} support"
+ cleanup
+ exit 2
+fi
+
+test_with_record_tpebs() {
+ echo "Testing with --record-tpebs"
+ if ! perf stat -e "${event}:R" --record-tpebs -a sleep 0.01 > "${stat_output}" 2>&1
+ then
+ echo "Testing with --record-tpebs [Failed perf stat]"
+ cat "${stat_output}"
+ exit 1
+ fi
+
+ # Expected output:
+ # $ perf stat --record-tpebs -e cache-misses:R -a sleep 0.01
+ # Events enabled
+ # [ perf record: Woken up 2 times to write data ]
+ # [ perf record: Captured and wrote 0.056 MB - ]
+ #
+ # Performance counter stats for 'system wide':
+ #
+ # 0 cache-misses:R
+ #
+ # 0.013963299 seconds time elapsed
+ if ! grep "perf record" "${stat_output}"
+ then
+ echo "Testing with --record-tpebs [Failed missing perf record]"
+ cat "${stat_output}"
+ exit 1
+ fi
+ if ! grep "${event}:R" "${stat_output}" && ! grep "/${event}/R" "${stat_output}"
+ then
+ echo "Testing with --record-tpebs [Failed missing event name]"
+ cat "${stat_output}"
+ exit 1
+ fi
+ echo "Testing with --record-tpebs [Success]"
+}
+
+test_with_record_tpebs
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/test_task_analyzer.sh b/tools/perf/tests/shell/test_task_analyzer.sh
new file mode 100755
index 000000000..0314412e6
--- /dev/null
+++ b/tools/perf/tests/shell/test_task_analyzer.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+# perf script task-analyzer tests (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+tmpdir=$(mktemp -d /tmp/perf-script-task-analyzer-XXXXX)
+# TODO: perf script report only supports input from the CWD perf.data file, make
+# it support input from any file.
+perfdata="perf.data"
+csv="$tmpdir/csv"
+csvsummary="$tmpdir/csvsummary"
+err=0
+
+# set PERF_EXEC_PATH to find scripts in the source directory
+perfdir=$(dirname "$0")/../..
+if [ -e "$perfdir/scripts/python/Perf-Trace-Util" ]; then
+ export PERF_EXEC_PATH=$perfdir
+fi
+
+# Disable lsan to avoid warnings about python memory leaks.
+export ASAN_OPTIONS=detect_leaks=0
+
+cleanup() {
+ rm -f "${perfdata}"
+ rm -f "${perfdata}".old
+ rm -rf "$tmpdir"
+
+ trap - exit term int
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup exit term int
+
+report() {
+ if [ "$1" = 0 ]; then
+ echo "PASS: \"$2\""
+ else
+ echo "FAIL: \"$2\" Error message: \"$3\""
+ err=1
+ fi
+}
+
+check_exec_0() {
+ if [ $? != 0 ]; then
+ report 1 "invocation of $1 command failed"
+ fi
+}
+
+find_str_or_fail() {
+ grep -q "$1" "$2"
+ if [ "$?" != 0 ]; then
+ report 1 "$3" "Failed to find required string:'${1}'."
+ else
+ report 0 "$3"
+ fi
+}
+
+# check if perf is compiled with libtraceevent support
+skip_no_probe_record_support() {
+ perf check feature -q libtraceevent && return 0
+ return 2
+}
+
+prepare_perf_data() {
+ # 1s should be sufficient to catch at least some switches
+ perf record -e sched:sched_switch -a -o "${perfdata}" -- sleep 1 > /dev/null 2>&1
+ # check if perf data file got created in above step.
+ if [ ! -e "${perfdata}" ]; then
+ printf "FAIL: perf record failed to create \"${perfdata}\" \n"
+ return 1
+ fi
+}
+
+# check standard inkvokation with no arguments
+test_basic() {
+ out="$tmpdir/perf.out"
+ perf script report task-analyzer > "$out"
+ check_exec_0 "perf script report task-analyzer"
+ find_str_or_fail "Comm" "$out" "${FUNCNAME[0]}"
+}
+
+test_ns_rename(){
+ out="$tmpdir/perf.out"
+ perf script report task-analyzer --ns --rename-comms-by-tids 0:random > "$out"
+ check_exec_0 "perf script report task-analyzer --ns --rename-comms-by-tids 0:random"
+ find_str_or_fail "Comm" "$out" "${FUNCNAME[0]}"
+}
+
+test_ms_filtertasks_highlight(){
+ out="$tmpdir/perf.out"
+ perf script report task-analyzer --ms --filter-tasks perf --highlight-tasks perf \
+ > "$out"
+ check_exec_0 "perf script report task-analyzer --ms --filter-tasks perf --highlight-tasks perf"
+ find_str_or_fail "Comm" "$out" "${FUNCNAME[0]}"
+}
+
+test_extended_times_timelimit_limittasks() {
+ out="$tmpdir/perf.out"
+ perf script report task-analyzer --extended-times --time-limit :99999 \
+ --limit-to-tasks perf > "$out"
+ check_exec_0 "perf script report task-analyzer --extended-times --time-limit :99999 --limit-to-tasks perf"
+ find_str_or_fail "Out-Out" "$out" "${FUNCNAME[0]}"
+}
+
+test_summary() {
+ out="$tmpdir/perf.out"
+ perf script report task-analyzer --summary > "$out"
+ check_exec_0 "perf script report task-analyzer --summary"
+ find_str_or_fail "Summary" "$out" "${FUNCNAME[0]}"
+}
+
+test_summaryextended() {
+ out="$tmpdir/perf.out"
+ perf script report task-analyzer --summary-extended > "$out"
+ check_exec_0 "perf script report task-analyzer --summary-extended"
+ find_str_or_fail "Inter Task Times" "$out" "${FUNCNAME[0]}"
+}
+
+test_summaryonly() {
+ out="$tmpdir/perf.out"
+ perf script report task-analyzer --summary-only > "$out"
+ check_exec_0 "perf script report task-analyzer --summary-only"
+ find_str_or_fail "Summary" "$out" "${FUNCNAME[0]}"
+}
+
+test_extended_times_summary_ns() {
+ out="$tmpdir/perf.out"
+ perf script report task-analyzer --extended-times --summary --ns > "$out"
+ check_exec_0 "perf script report task-analyzer --extended-times --summary --ns"
+ find_str_or_fail "Out-Out" "$out" "${FUNCNAME[0]}"
+ find_str_or_fail "Summary" "$out" "${FUNCNAME[0]}"
+}
+
+test_csv() {
+ perf script report task-analyzer --csv "${csv}" > /dev/null
+ check_exec_0 "perf script report task-analyzer --csv ${csv}"
+ find_str_or_fail "Comm;" "${csv}" "${FUNCNAME[0]}"
+}
+
+test_csv_extended_times() {
+ perf script report task-analyzer --csv "${csv}" --extended-times > /dev/null
+ check_exec_0 "perf script report task-analyzer --csv ${csv} --extended-times"
+ find_str_or_fail "Out-Out;" "${csv}" "${FUNCNAME[0]}"
+}
+
+test_csvsummary() {
+ perf script report task-analyzer --csv-summary "${csvsummary}" > /dev/null
+ check_exec_0 "perf script report task-analyzer --csv-summary ${csvsummary}"
+ find_str_or_fail "Comm;" "${csvsummary}" "${FUNCNAME[0]}"
+}
+
+test_csvsummary_extended() {
+ perf script report task-analyzer --csv-summary "${csvsummary}" --summary-extended \
+ >/dev/null
+ check_exec_0 "perf script report task-analyzer --csv-summary ${csvsummary} --summary-extended"
+ find_str_or_fail "Out-Out;" "${csvsummary}" "${FUNCNAME[0]}"
+}
+
+skip_no_probe_record_support
+err=$?
+if [ $err -ne 0 ]; then
+ echo "WARN: Skipping tests. No libtraceevent support"
+ cleanup
+ exit $err
+fi
+prepare_perf_data
+test_basic
+test_ns_rename
+test_ms_filtertasks_highlight
+test_extended_times_timelimit_limittasks
+test_summary
+test_summaryextended
+test_summaryonly
+test_extended_times_summary_ns
+test_csv
+test_csvsummary
+test_csv_extended_times
+test_csvsummary_extended
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/test_test_junit_output.sh b/tools/perf/tests/shell/test_test_junit_output.sh
new file mode 100755
index 000000000..5104ac1e1
--- /dev/null
+++ b/tools/perf/tests/shell/test_test_junit_output.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf test junit XML output validation
+
+set -e
+
+err=0
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/setup_python.sh
+. "${shelldir}"/lib/setup_python.sh
+
+result=$(mktemp /tmp/__perf_test.output.xml.XXXXX)
+
+cleanup()
+{
+ rm -f "${result}"
+ trap - exit term int
+}
+
+trap_cleanup()
+{
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup exit term int
+
+test_junit_output()
+{
+ echo "Testing perf test JUnit XML output command"
+ perf test -v -j"$result" util || true
+ if [ -s "$result" ] ; then
+ echo "perf test JUnit XML output command [SUCCESS]"
+ else
+ echo "perf test JUnit XML output command [FAILED]"
+ err=1
+ fi
+}
+
+validate_xml_format()
+{
+ echo "Validating perf test converted JUnit XML file"
+ if [ -f "$result" ] ; then
+ if $PYTHON -c \
+ "import xml.etree.ElementTree as ET; ET.parse('$result')" \
+ >/dev/null 2>&1 ; then
+ echo "The file contains valid XML format [SUCCESS]"
+ else
+ echo "The file does not contain valid XML format [FAILED]"
+ err=1
+ fi
+ else
+ echo "File not found [FAILED]"
+ err=1
+ fi
+}
+
+test_junit_output
+validate_xml_format
+
+cleanup
+exit ${err}
diff --git a/tools/perf/tests/shell/test_uprobe_from_different_cu.sh b/tools/perf/tests/shell/test_uprobe_from_different_cu.sh
new file mode 100755
index 000000000..7adf9755d
--- /dev/null
+++ b/tools/perf/tests/shell/test_uprobe_from_different_cu.sh
@@ -0,0 +1,89 @@
+#!/bin/bash
+# test perf probe of function from different CU
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+# shellcheck source=lib/probe.sh
+. "$(dirname $0)"/lib/probe.sh
+
+skip_if_no_perf_probe || exit 2
+[ "$(id -u)" == 0 ] || exit 2
+
+# skip if there's no gcc
+if ! [ -x "$(command -v gcc)" ]; then
+ echo "failed: no gcc compiler"
+ exit 2
+fi
+
+temp_dir=$(mktemp -d /tmp/perf-uprobe-different-cu-sh.XXXXXXXXXX)
+
+cleanup()
+{
+ trap - EXIT TERM INT
+ if [[ "${temp_dir}" =~ ^/tmp/perf-uprobe-different-cu-sh.*$ ]]; then
+ echo "--- Cleaning up ---"
+ perf probe -x ${temp_dir}/testfile -d foo || true
+ rm -f "${temp_dir}/"*
+ rmdir "${temp_dir}"
+ fi
+}
+
+trap_cleanup()
+{
+ cleanup
+ exit 1
+}
+
+trap trap_cleanup EXIT TERM INT
+
+cat > ${temp_dir}/testfile-foo.h << EOF
+struct t
+{
+ int *p;
+ int c;
+};
+
+extern int foo (int i, struct t *t);
+EOF
+
+cat > ${temp_dir}/testfile-foo.c << EOF
+#include "testfile-foo.h"
+
+int
+foo (int i, struct t *t)
+{
+ int j, res = 0;
+ for (j = 0; j < i && j < t->c; j++)
+ res += t->p[j];
+
+ return res;
+}
+EOF
+
+cat > ${temp_dir}/testfile-main.c << EOF
+#include "testfile-foo.h"
+
+static struct t g;
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ int j[argc];
+ g.c = argc;
+ g.p = j;
+ for (i = 0; i < argc; i++)
+ j[i] = (int) argv[i][0];
+ return foo (3, &g);
+}
+EOF
+
+gcc -g -Og -flto -c ${temp_dir}/testfile-foo.c -o ${temp_dir}/testfile-foo.o
+gcc -g -Og -c ${temp_dir}/testfile-main.c -o ${temp_dir}/testfile-main.o
+gcc -g -Og -o ${temp_dir}/testfile ${temp_dir}/testfile-foo.o ${temp_dir}/testfile-main.o
+
+perf probe -x ${temp_dir}/testfile --funcs foo | grep "foo"
+perf probe -x ${temp_dir}/testfile foo
+
+cleanup
diff --git a/tools/perf/tests/shell/timechart.sh b/tools/perf/tests/shell/timechart.sh
new file mode 100755
index 000000000..40072b321
--- /dev/null
+++ b/tools/perf/tests/shell/timechart.sh
@@ -0,0 +1,73 @@
+#!/bin/bash
+# perf timechart tests
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+perfdata=$(mktemp /tmp/__perf_timechart_test.perf.data.XXXXX)
+output=$(mktemp /tmp/__perf_timechart_test.output.XXXXX.svg)
+
+cleanup() {
+ rm -f "${perfdata}"*
+ rm -f "${output}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_timechart() {
+ NAME=$1
+ OPTION=$2
+
+ echo "perf timechart ${NAME} test"
+
+ # Try to record timechart data.
+ # perf timechart record uses system-wide recording and specific tracepoints.
+ # If it fails (e.g. permissions, missing tracepoints), skip the test.
+ if ! perf timechart record -o "${perfdata}" ${OPTION} true > /dev/null 2>&1; then
+ echo "perf timechart ${NAME} test [Skipped: perf timechart record failed (permissions/events?)]"
+ return
+ fi
+
+ # Generate the timechart
+ if ! perf timechart -i "${perfdata}" -o "${output}" > /dev/null 2>&1; then
+ echo "perf timechart ${NAME} test [Failed: perf timechart command failed]"
+ err=1
+ return
+ fi
+
+ # Check if output file exists and is not empty
+ if [ ! -s "${output}" ]; then
+ echo "perf timechart ${NAME} test [Failed: output file is empty or missing]"
+ err=1
+ return
+ fi
+
+ # Check if it looks like an SVG
+ if ! grep -q "svg" "${output}"; then
+ echo "perf timechart ${NAME} test [Failed: output doesn't look like SVG]"
+ err=1
+ return
+ fi
+
+ echo "perf timechart ${NAME} test [Success]"
+}
+
+if ! perf check feature -q libtraceevent ; then
+ echo "perf timechart is not supported. Skip."
+ cleanup
+ exit 2
+fi
+
+test_timechart "Basic" ""
+test_timechart "IO-only" "-I"
+test_timechart "Backtrace" "-g"
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/top.sh b/tools/perf/tests/shell/top.sh
new file mode 100755
index 000000000..ad7fccd09
--- /dev/null
+++ b/tools/perf/tests/shell/top.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+# perf top tests (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+err=0
+log_file=$(mktemp /tmp/perf.top.log.XXXXX)
+
+cleanup() {
+ rm -f "${log_file}"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_basic_perf_top() {
+ echo "Basic perf top test"
+
+ # Start a workload that spins to generate samples
+ # thloop runs for the specified number of seconds
+ perf test -w thloop 20 &
+ PID=$!
+
+ # Allow it to start
+ sleep 0.1
+
+ # Run perf top for 5 seconds, monitoring that PID
+ # Use --stdio to avoid TUI and redirect output
+ # Use -d 1 to avoid flooding output
+ # Use -e cpu-clock to ensure we get samples
+ # Use sleep to keep stdin open but silent, preventing EOF loop or interactive spam
+ if ! sleep 10 | timeout 5s perf top --stdio -d 1 -e cpu-clock -p $PID > "${log_file}" 2>&1; then
+ retval=$?
+ if [ $retval -ne 124 ] && [ $retval -ne 0 ]; then
+ echo "Basic perf top test [Failed: perf top failed to start or run (ret=$retval)]"
+ head -n 50 "${log_file}"
+ kill $PID
+ wait $PID 2>/dev/null || true
+ err=1
+ return
+ fi
+ fi
+
+ kill $PID
+ wait $PID 2>/dev/null || true
+
+ # Check for some sample data (percentage)
+ if ! grep -E -q "[0-9]+\.[0-9]+%" "${log_file}"; then
+ echo "Basic perf top test [Failed: no sample percentage found]"
+ head -n 50 "${log_file}"
+ err=1
+ return
+ fi
+
+ # Check for the symbol
+ if ! grep -q "test_loop" "${log_file}"; then
+ echo "Basic perf top test [Failed: test_loop symbol not found]"
+ head -n 50 "${log_file}"
+ err=1
+ return
+ fi
+
+ echo "Basic perf top test [Success]"
+}
+
+test_basic_perf_top
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/trace+probe_vfs_getname.sh b/tools/perf/tests/shell/trace+probe_vfs_getname.sh
new file mode 100755
index 000000000..7a0b1145d
--- /dev/null
+++ b/tools/perf/tests/shell/trace+probe_vfs_getname.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+# Check open filename arg using perf trace + vfs_getname (exclusive)
+
+# Uses the 'perf test shell' library to add probe:vfs_getname to the system
+# then use it with 'perf trace' using 'touch' to write to a temp file, then
+# checks that that was captured by the vfs_getname was used by 'perf trace',
+# that already handles "probe:vfs_getname" if present, and used in the
+# "open" syscall "filename" argument beautifier.
+
+# SPDX-License-Identifier: GPL-2.0
+# Arnaldo Carvalho de Melo <acme@kernel.org>, 2017
+
+# shellcheck source=lib/probe.sh
+. "$(dirname $0)"/lib/probe.sh
+
+skip_if_no_perf_probe || exit 2
+skip_if_no_perf_trace || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+. "$(dirname $0)"/lib/probe_vfs_getname.sh
+
+trace_open_vfs_getname() {
+ evts="$(echo "$(perf list tracepoint 2>/dev/null | grep -E 'syscalls:sys_enter_open(at)? ' | sed -r 's/.*sys_enter_([a-z]+) +\[.*$/\1/')" | sed ':a;N;s:\n:,:g')"
+ perf trace -e $evts touch $file 2>&1 | \
+ grep -E " +[0-9]+\.[0-9]+ +\( +[0-9]+\.[0-9]+ ms\): +touch/[0-9]+ open(at)?\((dfd: +CWD, +)?filename: +\"?${file}\"?, +flags: CREAT\|NOCTTY\|NONBLOCK\|WRONLY, +mode: +IRUGO\|IWUGO\) += +[0-9]+$"
+}
+
+add_probe_vfs_getname
+err=$?
+
+if [ $err -eq 1 ] ; then
+ skip_if_no_debuginfo
+ err=$?
+fi
+
+if [ $err -ne 0 ] ; then
+ exit $err
+fi
+
+file=$(mktemp /tmp/temporary_file.XXXXX)
+
+# Do not use whatever ~/.perfconfig file, it may change the output
+# via trace.{show_timestamp,show_prefix,etc}
+export PERF_CONFIG=/dev/null
+
+trace_open_vfs_getname
+err=$?
+rm -f ${file}
+cleanup_probe_vfs_getname
+exit $err
diff --git a/tools/perf/tests/shell/trace_btf_enum.sh b/tools/perf/tests/shell/trace_btf_enum.sh
new file mode 100755
index 000000000..03e9f680a
--- /dev/null
+++ b/tools/perf/tests/shell/trace_btf_enum.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+# perf trace enum augmentation tests
+# SPDX-License-Identifier: GPL-2.0
+
+err=0
+
+syscall="landlock_add_rule"
+non_syscall="timer:hrtimer_start"
+
+TESTPROG="perf test -w landlock"
+
+# shellcheck source=lib/probe.sh
+. "$(dirname $0)"/lib/probe.sh
+skip_if_no_perf_trace || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+check_vmlinux() {
+ echo "Checking if vmlinux exists"
+ if [ ! -f /sys/kernel/btf/vmlinux ]
+ then
+ echo "trace+enum test [Skipped missing vmlinux BTF support]"
+ err=2
+ fi
+}
+
+check_permissions() {
+ if perf trace -e $syscall $TESTPROG 2>&1 | grep -q "Operation not permitted"
+ then
+ echo "trace+enum test [Skipped permissions]"
+ err=2
+ fi
+}
+
+trace_landlock() {
+ echo "Tracing syscall ${syscall}"
+
+ # test flight just to see if landlock_add_rule is available
+ if ! perf trace $TESTPROG 2>&1 | grep -q landlock
+ then
+ echo "No landlock system call found, skipping to non-syscall tracing."
+ return
+ fi
+
+ output="$(perf trace -e $syscall $TESTPROG 2>&1)"
+ if echo "$output" | grep -q -E ".*landlock_add_rule\(ruleset_fd: 11, rule_type: (LANDLOCK_RULE_PATH_BENEATH|LANDLOCK_RULE_NET_PORT), rule_attr: 0x[a-f0-9]+, flags: 45\) = -1.*"
+ then
+ err=0
+ else
+ printf "[syscall failure] Failed to trace syscall $syscall, output:\n$output\n"
+ err=1
+ fi
+}
+
+trace_non_syscall() {
+ echo "Tracing non-syscall tracepoint ${non_syscall}"
+ output="$(perf trace -e $non_syscall --max-events=1 2>&1)"
+ if echo "$output" | grep -q -E '.*timer:hrtimer_.*\(.*mode: HRTIMER_MODE_.*\)$'
+ then
+ err=0
+ else
+ printf "[tracepoint failure] Failed to trace tracepoint $non_syscall, output:\n$output\n"
+ err=1
+ fi
+}
+
+check_vmlinux
+if [ $err = 0 ]; then
+ check_permissions
+fi
+
+if [ $err = 0 ]; then
+ trace_landlock
+fi
+
+if [ $err = 0 ]; then
+ trace_non_syscall
+fi
+
+exit $err
diff --git a/tools/perf/tests/shell/trace_btf_general.sh b/tools/perf/tests/shell/trace_btf_general.sh
new file mode 100755
index 000000000..7a94a5743
--- /dev/null
+++ b/tools/perf/tests/shell/trace_btf_general.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+# perf trace BTF general tests (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+err=0
+
+# shellcheck source=lib/probe.sh
+. "$(dirname $0)"/lib/probe.sh
+
+file1=$(mktemp /tmp/file1_XXXX)
+file2=$(echo $file1 | sed 's/file1/file2/g')
+
+buffer="buffer content"
+perf_config_tmp=$(mktemp /tmp/.perfconfig_XXXXX)
+
+trap cleanup EXIT TERM INT HUP
+
+check_vmlinux() {
+ echo "Checking if vmlinux BTF exists"
+ if [ ! -f /sys/kernel/btf/vmlinux ]
+ then
+ echo "Skipped due to missing vmlinux BTF"
+ return 2
+ fi
+ return 0
+}
+
+trace_test_string() {
+ echo "Testing perf trace's string augmentation"
+ output="$(perf trace --sort-events -e renameat* --max-events=1 -- mv ${file1} ${file2} 2>&1)"
+ if ! echo "$output" | grep -q -E "^mv/[0-9]+ renameat(2)?\(.*, \"${file1}\", .*, \"${file2}\", .*\) += +[0-9]+$"
+ then
+ printf "String augmentation test failed, output:\n$output\n"
+ err=1
+ fi
+}
+
+trace_test_buffer() {
+ echo "Testing perf trace's buffer augmentation"
+ # echo will insert a newline (\10) at the end of the buffer
+ output="$(perf trace --sort-events -e write --max-events=1 -- echo "${buffer}" 2>&1)"
+ if ! echo "$output" | grep -q -E "^echo/[0-9]+ write\([0-9]+, ${buffer}.*, [0-9]+\) += +[0-9]+$"
+ then
+ printf "Buffer augmentation test failed, output:\n$output\n"
+ err=1
+ fi
+}
+
+trace_test_struct_btf() {
+ echo "Testing perf trace's struct augmentation"
+ output="$(perf trace --sort-events -e clock_nanosleep --force-btf --max-events=1 -- sleep 1 2>&1)"
+ if ! echo "$output" | grep -q -E "^sleep/[0-9]+ clock_nanosleep\(0, 0, \{1,.*\}, 0x[0-9a-f]+\) += +[0-9]+$"
+ then
+ printf "BTF struct augmentation test failed, output:\n$output\n"
+ err=1
+ fi
+}
+
+cleanup() {
+ rm -rf ${file1} ${file2} ${perf_config_tmp}
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+
+# don't overwrite user's perf config
+trace_config() {
+ export PERF_CONFIG=${perf_config_tmp}
+ perf config trace.show_arg_names=false trace.show_duration=false \
+ trace.show_timestamp=false trace.args_alignment=0
+}
+
+skip_if_no_perf_trace || exit 2
+check_vmlinux || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+trace_config
+
+trace_test_string
+
+if [ $err = 0 ]; then
+ trace_test_buffer
+fi
+
+if [ $err = 0 ]; then
+ trace_test_struct_btf
+fi
+
+cleanup
+
+exit $err
diff --git a/tools/perf/tests/shell/trace_exit_race.sh b/tools/perf/tests/shell/trace_exit_race.sh
new file mode 100755
index 000000000..db300cde9
--- /dev/null
+++ b/tools/perf/tests/shell/trace_exit_race.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# perf trace exit race
+# SPDX-License-Identifier: GPL-2.0
+
+# Check that the last events of a perf trace'd subprocess are not
+# lost. Specifically, trace the exiting syscall of "true" 10 times and ensure
+# the output contains 10 correct lines.
+
+# shellcheck source=lib/probe.sh
+. "$(dirname $0)"/lib/probe.sh
+
+skip_if_no_perf_trace || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+if [ "$1" = "-v" ]; then
+ verbose="1"
+fi
+
+iter=10
+regexp=" +[0-9]+\.[0-9]+ [0-9]+ syscalls:sys_enter_exit_group\(\)$"
+
+trace_shutdown_race() {
+ for _ in $(seq $iter); do
+ perf trace --no-comm -e syscalls:sys_enter_exit_group true 2>>$file
+ done
+ result="$(grep -c -E "$regexp" $file)"
+ [ $result = $iter ]
+}
+
+
+file=$(mktemp /tmp/temporary_file.XXXXX)
+
+# Do not use whatever ~/.perfconfig file, it may change the output
+# via trace.{show_timestamp,show_prefix,etc}
+export PERF_CONFIG=/dev/null
+
+trace_shutdown_race
+err=$?
+
+if [ $err != 0 ] && [ "${verbose}" = "1" ]; then
+ lines_not_matching=$(mktemp /tmp/temporary_file.XXXXX)
+ if grep -v -E "$regexp" $file > $lines_not_matching ; then
+ echo "Lines not matching the expected regexp: '$regexp':"
+ cat $lines_not_matching
+ else
+ echo "Missing output, expected $iter but only got $result"
+ fi
+ rm -f $lines_not_matching
+fi
+
+rm -f ${file}
+exit $err
diff --git a/tools/perf/tests/shell/trace_record_replay.sh b/tools/perf/tests/shell/trace_record_replay.sh
new file mode 100755
index 000000000..38fcafcdf
--- /dev/null
+++ b/tools/perf/tests/shell/trace_record_replay.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+# perf trace record and replay
+# SPDX-License-Identifier: GPL-2.0
+
+# Check that perf trace works with record and replay
+
+# shellcheck source=lib/probe.sh
+. "$(dirname $0)"/lib/probe.sh
+# shellcheck source=lib/perf_record.sh
+. "$(dirname $0)"/lib/perf_record.sh
+
+skip_if_no_perf_trace || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+file=$(mktemp /tmp/temporary_file.XXXXX)
+err=0
+
+cleanup() {
+ rm -f ${file}
+ perf_record_cleanup
+ trap - EXIT INT TERM
+}
+
+trap_cleanup() {
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT INT TERM
+
+check_nanosleep() {
+ perf trace -i "${file}" 2>&1 | grep -q nanosleep
+}
+
+PERF_RECORD_CMD="perf trace record" perf_record_with_retry "${file}" "check_nanosleep" "sleep"
+err=$?
+
+if [ $err -ne 0 ]; then
+ if [ $err -eq 2 ]; then
+ logfile="${PERF_RECORD_LOGS[${#PERF_RECORD_LOGS[@]}-1]}"
+ echo "perf trace record failed. Log output:"
+ cat "$logfile"
+ else
+ echo "Failed: cannot find *nanosleep syscall"
+ fi
+ cleanup
+ exit 1
+fi
+
+cleanup
+exit 0
diff --git a/tools/perf/tests/shell/trace_summary.sh b/tools/perf/tests/shell/trace_summary.sh
new file mode 100755
index 000000000..b80dea77c
--- /dev/null
+++ b/tools/perf/tests/shell/trace_summary.sh
@@ -0,0 +1,77 @@
+#!/bin/bash
+# perf trace summary (exclusive)
+# SPDX-License-Identifier: GPL-2.0
+
+# Check that perf trace works with various summary mode
+
+# shellcheck source=lib/probe.sh
+. "$(dirname $0)"/lib/probe.sh
+
+skip_if_no_perf_trace || exit 2
+[ "$(id -u)" = 0 ] || exit 2
+
+OUTPUT=$(mktemp /tmp/perf_trace_test.XXXXX)
+
+test_perf_trace() {
+ args=$1
+ workload="cat /dev/null"
+ search="^\s*(open|read|close).*[0-9]+%$"
+
+ echo "testing: perf trace ${args} -- ${workload}"
+ perf trace ${args} -- ${workload} >${OUTPUT} 2>&1
+ if [ $? -ne 0 ]; then
+ echo "Error: perf trace ${args} failed unexpectedly"
+ cat ${OUTPUT}
+ rm -f ${OUTPUT}
+ exit 1
+ fi
+
+ count=$(grep -E -c -m 3 "${search}" ${OUTPUT})
+ if [ "${count}" != "3" ]; then
+ echo "Error: cannot find enough pattern ${search} in the output"
+ cat ${OUTPUT}
+ rm -f ${OUTPUT}
+ exit 1
+ fi
+}
+
+# summary only for a process
+test_perf_trace "-s"
+
+# normal output with summary at the end
+test_perf_trace "-S"
+
+# summary only with an explicit summary mode
+test_perf_trace "-s --summary-mode=thread"
+
+# summary with normal output - total summary mode
+test_perf_trace "-S --summary-mode=total"
+
+# summary only for system wide - per-thread summary
+test_perf_trace "-as --summary-mode=thread --no-bpf-summary"
+
+# summary only for system wide - total summary mode
+test_perf_trace "-as --summary-mode=total --no-bpf-summary"
+
+if ! perf check feature -q bpf; then
+ echo "Skip --bpf-summary tests as perf built without libbpf"
+ rm -f ${OUTPUT}
+ exit 2
+fi
+
+# summary only for system wide - per-thread summary with BPF
+test_perf_trace "-as --summary-mode=thread --bpf-summary"
+
+# summary only for system wide - total summary mode with BPF
+test_perf_trace "-as --summary-mode=total --bpf-summary"
+
+# summary with normal output for system wide - total summary mode with BPF
+test_perf_trace "-aS --summary-mode=total --bpf-summary"
+
+# summary only for system wide - cgroup summary mode with BPF
+test_perf_trace "-as --summary-mode=cgroup --bpf-summary"
+
+# summary with normal output for system wide - cgroup summary mode with BPF
+test_perf_trace "-aS --summary-mode=cgroup --bpf-summary"
+
+rm -f ${OUTPUT}