| #!/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 |