1#!/bin/sh
2# Miscellaneous Intel PT testing
3# SPDX-License-Identifier: GPL-2.0
4
5set -e
6
7# Skip if no Intel PT
8perf list | grep -q 'intel_pt//' || exit 2
9
10shelldir=$(dirname "$0")
11. "${shelldir}"/lib/waiting.sh
12
13skip_cnt=0
14ok_cnt=0
15err_cnt=0
16
17temp_dir=$(mktemp -d /tmp/perf-test-intel-pt-sh.XXXXXXXXXX)
18
19tmpfile="${temp_dir}/tmp-perf.data"
20perfdatafile="${temp_dir}/test-perf.data"
21outfile="${temp_dir}/test-out.txt"
22errfile="${temp_dir}/test-err.txt"
23workload="${temp_dir}/workload"
24awkscript="${temp_dir}/awkscript"
25
26cleanup()
27{
28	trap - EXIT TERM INT
29	sane=$(echo "${temp_dir}" | cut -b 1-26)
30	if [ "${sane}" = "/tmp/perf-test-intel-pt-sh" ] ; then
31		echo "--- Cleaning up ---"
32		rm -f "${temp_dir}/"*
33		rmdir "${temp_dir}"
34	fi
35}
36
37trap_cleanup()
38{
39	cleanup
40	exit 1
41}
42
43trap trap_cleanup EXIT TERM INT
44
45have_workload=false
46cat << _end_of_file_ | /usr/bin/cc -o "${workload}" -xc - -pthread && have_workload=true
47#include <time.h>
48#include <pthread.h>
49
50void work(void) {
51	struct timespec tm = {
52		.tv_nsec = 1000000,
53	};
54	int i;
55
56	/* Run for about 30 seconds */
57	for (i = 0; i < 30000; i++)
58		nanosleep(&tm, NULL);
59}
60
61void *threadfunc(void *arg) {
62	work();
63	return NULL;
64}
65
66int main(void) {
67	pthread_t th;
68
69	pthread_create(&th, NULL, threadfunc, NULL);
70	work();
71	pthread_join(th, NULL);
72	return 0;
73}
74_end_of_file_
75
76can_cpu_wide()
77{
78	echo "Checking for CPU-wide recording on CPU $1"
79	if ! perf record -o "${tmpfile}" -B -N --no-bpf-event -e dummy:u -C "$1" true >/dev/null 2>&1 ; then
80		echo "No so skipping"
81		return 2
82	fi
83	echo OK
84	return 0
85}
86
87test_system_wide_side_band()
88{
89	echo "--- Test system-wide sideband ---"
90
91	# Need CPU 0 and CPU 1
92	can_cpu_wide 0 || return $?
93	can_cpu_wide 1 || return $?
94
95	# Record on CPU 0 a task running on CPU 1
96	perf record -B -N --no-bpf-event -o "${perfdatafile}" -e intel_pt//u -C 0 -- taskset --cpu-list 1 uname
97
98	# Should get MMAP events from CPU 1 because they can be needed to decode
99	mmap_cnt=$(perf script -i "${perfdatafile}" --no-itrace --show-mmap-events -C 1 2>/dev/null | grep -c MMAP)
100
101	if [ "${mmap_cnt}" -gt 0 ] ; then
102		echo OK
103		return 0
104	fi
105
106	echo "Failed to record MMAP events on CPU 1 when tracing CPU 0"
107	return 1
108}
109
110can_kernel()
111{
112	perf record -o "${tmpfile}" -B -N --no-bpf-event -e dummy:k true >/dev/null 2>&1 || return 2
113	return 0
114}
115
116test_per_thread()
117{
118	k="$1"
119	desc="$2"
120
121	echo "--- Test per-thread ${desc}recording ---"
122
123	if ! $have_workload ; then
124		echo "No workload, so skipping"
125		return 2
126	fi
127
128	if [ "${k}" = "k" ] ; then
129		can_kernel || return 2
130	fi
131
132	cat <<- "_end_of_file_" > "${awkscript}"
133	BEGIN {
134		s = "[ ]*"
135		u = s"[0-9]+"s
136		d = s"[0-9-]+"s
137		x = s"[0-9a-fA-FxX]+"s
138		mmapping = "idx"u": mmapping fd"u
139		set_output = "idx"u": set output fd"u"->"u
140		perf_event_open = "sys_perf_event_open: pid"d"cpu"d"group_fd"d"flags"x"="u
141	}
142
143	/perf record opening and mmapping events/ {
144		if (!done)
145			active = 1
146	}
147
148	/perf record done opening and mmapping events/ {
149		active = 0
150		done = 1
151	}
152
153	$0 ~ perf_event_open && active {
154		match($0, perf_event_open)
155		$0 = substr($0, RSTART, RLENGTH)
156		pid = $3
157		cpu = $5
158		fd = $11
159		print "pid " pid " cpu " cpu " fd " fd " : " $0
160		fd_array[fd] = fd
161		pid_array[fd] = pid
162		cpu_array[fd] = cpu
163	}
164
165	$0 ~ mmapping && active  {
166		match($0, mmapping)
167		$0 = substr($0, RSTART, RLENGTH)
168		fd = $5
169		print "fd " fd " : " $0
170		if (fd in fd_array) {
171			mmap_array[fd] = 1
172		} else {
173			print "Unknown fd " fd
174			exit 1
175		}
176	}
177
178	$0 ~ set_output && active {
179		match($0, set_output)
180		$0 = substr($0, RSTART, RLENGTH)
181		fd = $6
182		fd_to = $8
183		print "fd " fd " fd_to " fd_to " : " $0
184		if (fd in fd_array) {
185			if (fd_to in fd_array) {
186				set_output_array[fd] = fd_to
187			} else {
188				print "Unknown fd " fd_to
189				exit 1
190			}
191		} else {
192			print "Unknown fd " fd
193			exit 1
194		}
195	}
196
197	END {
198		print "Checking " length(fd_array) " fds"
199		for (fd in fd_array) {
200			if (fd in mmap_array) {
201				pid = pid_array[fd]
202				if (pid != -1) {
203					if (pid in pids) {
204						print "More than 1 mmap for PID " pid
205						exit 1
206					}
207					pids[pid] = 1
208				}
209				cpu = cpu_array[fd]
210				if (cpu != -1) {
211					if (cpu in cpus) {
212						print "More than 1 mmap for CPU " cpu
213						exit 1
214					}
215					cpus[cpu] = 1
216				}
217			} else if (!(fd in set_output_array)) {
218				print "No mmap for fd " fd
219				exit 1
220			}
221		}
222		n = length(pids)
223		if (n != thread_cnt) {
224			print "Expected " thread_cnt " per-thread mmaps - found " n
225			exit 1
226		}
227	}
228	_end_of_file_
229
230	$workload &
231	w1=$!
232	$workload &
233	w2=$!
234	echo "Workload PIDs are $w1 and $w2"
235	wait_for_threads ${w1} 2
236	wait_for_threads ${w2} 2
237
238	perf record -B -N --no-bpf-event -o "${perfdatafile}" -e intel_pt//u"${k}" -vvv --per-thread -p "${w1},${w2}" 2>"${errfile}" >"${outfile}" &
239	ppid=$!
240	echo "perf PID is $ppid"
241	wait_for_perf_to_start ${ppid} "${errfile}" || return 1
242
243	kill ${w1}
244	wait_for_process_to_exit ${w1} || return 1
245	is_running ${ppid} || return 1
246
247	kill ${w2}
248	wait_for_process_to_exit ${w2} || return 1
249	wait_for_process_to_exit ${ppid} || return 1
250
251	awk -v thread_cnt=4 -f "${awkscript}" "${errfile}" || return 1
252
253	echo OK
254	return 0
255}
256
257count_result()
258{
259	if [ "$1" -eq 2 ] ; then
260		skip_cnt=$((skip_cnt + 1))
261		return
262	fi
263	if [ "$1" -eq 0 ] ; then
264		ok_cnt=$((ok_cnt + 1))
265		return
266	fi
267	err_cnt=$((err_cnt + 1))
268	ret=0
269}
270
271ret=0
272test_system_wide_side_band || ret=$? ; count_result $ret
273test_per_thread "" "" || ret=$? ; count_result $ret
274test_per_thread "k" "(incl. kernel) " || ret=$? ; count_result $ret
275
276cleanup
277
278echo "--- Done ---"
279
280if [ ${err_cnt} -gt 0 ] ; then
281	exit 1
282fi
283
284if [ ${ok_cnt} -gt 0 ] ; then
285	exit 0
286fi
287
288exit 2
289