xref: /linux/tools/perf/tests/shell/coresight/callchain.sh (revision 473f6c8f437b049f8ec015d57cd59bb983b1d85c)
1#!/bin/bash
2# CoreSight synthesized callchain (exclusive)
3# SPDX-License-Identifier: GPL-2.0
4
5glb_err=1
6
7if ! tmpdir=$(mktemp -d /tmp/perf-cs-callchain-test.XXXXXX); then
8	echo "mktemp failed"
9	exit 1
10fi
11
12cleanup_files()
13{
14	rm -rf "$tmpdir"
15}
16
17trap cleanup_files EXIT
18trap 'cleanup_files; exit $glb_err' TERM INT
19
20skip_if_system_is_not_ready()
21{
22	perf list | grep -Pzq 'cs_etm//' || {
23		echo "[Skip] cs_etm event is not available" >&2
24		return 2
25	}
26
27	# Requires root for trace in kernel
28	[ "$(id -u)" = 0 ] || {
29		echo "[Skip] No root permission" >&2
30		return 2
31	}
32
33	return 0
34}
35
36record_trace()
37{
38	local data=$1
39	local script=$2
40
41	local cf="$tmpdir/ctl"
42	local af="$tmpdir/ack"
43
44	mkfifo "$cf" "$af"
45
46	perf record -o "$data" -e cs_etm// --per-thread -D -1 --control fifo:"$cf","$af" -- \
47		perf test --record-ctl fifo:"$cf","$af" -w callchain >/dev/null 2>&1 &&
48
49	# It is safe to use 'i3i' with a three-instruction interval, since the
50	# workload is compiled with -O0.
51	perf script --itrace=g16i3il64 -i "$data" > "$script"
52}
53
54callchain_regex_1()
55{
56	printf '%s' \
57'perf[[:space:]]+[0-9]+[[:space:]]+\[[0-9]+\][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\
58'[[:space:]]+[[:xdigit:]]+ callchain_foo\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
59'[[:space:]]+[[:xdigit:]]+ callchain\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
60'([[:space:]]+[[:xdigit:]]+ .*\n)*'
61}
62
63callchain_regex_2()
64{
65	printf '%s' \
66'perf[[:space:]]+[0-9]+[[:space:]]+\[[0-9]+\][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\
67'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
68'[[:space:]]+[[:xdigit:]]+ callchain_foo\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
69'[[:space:]]+[[:xdigit:]]+ callchain\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
70'([[:space:]]+[[:xdigit:]]+ .*\n)*'
71}
72
73callchain_regex_3()
74{
75	printf '%s' \
76'perf[[:space:]]+[0-9]+[[:space:]]+\[[0-9]+\][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\
77'[[:space:]]+[[:xdigit:]]+ syscall(@plt)?\+0x[[:xdigit:]]+ \(.*\)\n'\
78'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
79'[[:space:]]+[[:xdigit:]]+ callchain_foo\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
80'[[:space:]]+[[:xdigit:]]+ callchain\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
81'([[:space:]]+[[:xdigit:]]+ .*\n)*'
82}
83
84callchain_regex_4()
85{
86	printf '%s' \
87'perf[[:space:]]+[0-9]+[[:space:]]+\[[0-9]+\][[:space:]]+([0-9.]+:[[:space:]]+)?[0-9]+ instructions:[[:space:]]*\n'\
88'[[:space:]]+[[:xdigit:]]+ .*\+0x[[:xdigit:]]+ \(\[kernel\.kallsyms\]\)\n'\
89'[[:space:]]+[[:xdigit:]]+ syscall(@plt)?\+0x[[:xdigit:]]+ \(.*\)\n'\
90'[[:space:]]+[[:xdigit:]]+ callchain_do_syscall\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
91'[[:space:]]+[[:xdigit:]]+ callchain_foo\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
92'[[:space:]]+[[:xdigit:]]+ callchain\+0x[[:xdigit:]]+ \(.*/perf\)\n'\
93'([[:space:]]+[[:xdigit:]]+ .*\n)*'
94}
95
96find_after_line()
97{
98	local regex="$1"
99	local file="$2"
100	local start="$3"
101	local offset
102	local line
103
104	# Search in byte offset
105	offset=$(
106		tail -n +"$start" "$file" |
107		grep -Pzob -m1 "$regex" |
108		tr '\0' '\n' |
109		sed -n 's/^\([0-9][0-9]*\):.*/\1/p;q'
110	)
111
112	if [ -z "$offset" ]; then
113		echo "Failed to match regex after line $start" >&2
114		echo "Regex:" >&2
115		printf '%s\n' "$regex" >&2
116		echo "Context from line $start:" >&2
117		sed -n "${start},$((start + 100))p" "$file" >&2
118		return 1
119	fi
120
121	# Convert from offset to line
122	line=$(
123		tail -n +"$start" "$file" |
124		head -c "$offset" |
125		wc -l
126	)
127
128	echo "$((start + line))"
129}
130
131check_callchain_flow()
132{
133	local file="$1"
134	local l1 l2 l3 l4 l5 l6 l7
135
136	# Callchain push
137	l1=$(find_after_line "$(callchain_regex_1)" "$file" 1) || return 1
138	l2=$(find_after_line "$(callchain_regex_2)" "$file" "$((l1 + 1))") || return 1
139	l3=$(find_after_line "$(callchain_regex_3)" "$file" "$((l2 + 1))") || return 1
140	l4=$(find_after_line "$(callchain_regex_4)" "$file" "$((l3 + 1))") || return 1
141
142	# Callchain pop
143	l5=$(find_after_line "$(callchain_regex_3)" "$file" "$((l4 + 1))") || return 1
144	l6=$(find_after_line "$(callchain_regex_2)" "$file" "$((l5 + 1))") || return 1
145	l7=$(find_after_line "$(callchain_regex_1)" "$file" "$((l6 + 1))") || return 1
146
147	echo "Callchain flow matched:"
148	echo "  l1=$l1 l2=$l2 l3=$l3 l4=$l4 l5=$l5 l6=$l6 l7=$l7"
149
150	return 0
151}
152
153run_test()
154{
155	local data=$tmpdir/perf.data
156	local script=$tmpdir/perf.script
157
158	if ! record_trace "$data" "$script"; then
159		echo "perf record/script failed"
160		return
161	fi
162
163	check_callchain_flow "$script" || return
164
165	glb_err=0
166}
167
168skip_if_system_is_not_ready || exit 2
169
170run_test
171
172exit $glb_err
173