diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
| commit | 3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch) | |
| tree | b767d7f6e26bc64334d3f14f8422d187239dc45d /tools/perf/tests/shell | |
| download | linux-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')
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} |
