mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 07:39:12 -04:00
Introduce `perf_record_with_retry` and `perf_record_cleanup` in a shared library `tests/shell/lib/perf_record.sh` to prevent record test failures caused by transient recording or workload delays. Update `record.sh`, `record_lbr.sh`, `pipe_test.sh`, `kvm.sh`, and `stat_all_pfm.sh` to use this robust record retry logic. These tests now start with very short durations (e.g. 0.01 seconds) and scale up if the initial recording failed to capture samples, significantly improving test execution speed on success while remaining resilient to slow systems. Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Ian Rogers <irogers@google.com> Signed-off-by: Namhyung Kim <namhyung@kernel.org>
468 lines
13 KiB
Bash
Executable File
468 lines
13 KiB
Bash
Executable File
#!/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_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_ratio_to_prev
|
|
|
|
# restore the default value
|
|
ulimit -Sn $default_fd_limit
|
|
|
|
cleanup
|
|
exit $err
|