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