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