Add a workload that creates two threads that read and write to a pipe so that every read/write results in the other thread being scheduled. If this is run on one CPU then the threads will alternate and we can test that processor trace always attributes symbols to the correct thread and that the attribution changes at the exact point that the thread switch happened.
Signed-off-by: James Clark james.clark@linaro.org --- tools/perf/tests/shell/coresight/Makefile | 1 + .../shell/coresight/context_switch_loop/.gitignore | 1 + .../shell/coresight/context_switch_loop/Makefile | 29 ++++++++ .../context_switch_loop/context_switch_loop.c | 83 ++++++++++++++++++++++ .../tests/shell/coresight/context_switch_thread.sh | 48 +++++++++++++ 5 files changed, 162 insertions(+)
diff --git a/tools/perf/tests/shell/coresight/Makefile b/tools/perf/tests/shell/coresight/Makefile index fa08fd9a5991..710e742d9def 100644 --- a/tools/perf/tests/shell/coresight/Makefile +++ b/tools/perf/tests/shell/coresight/Makefile @@ -6,6 +6,7 @@ include ../../../../../tools/scripts/utilities.mak
SUBDIRS = \ asm_pure_loop \ + context_switch_loop \ memcpy_thread \ thread_loop \ unroll_loop_thread diff --git a/tools/perf/tests/shell/coresight/context_switch_loop/.gitignore b/tools/perf/tests/shell/coresight/context_switch_loop/.gitignore new file mode 100644 index 000000000000..86a7becebba4 --- /dev/null +++ b/tools/perf/tests/shell/coresight/context_switch_loop/.gitignore @@ -0,0 +1 @@ +context_switch_loop diff --git a/tools/perf/tests/shell/coresight/context_switch_loop/Makefile b/tools/perf/tests/shell/coresight/context_switch_loop/Makefile new file mode 100644 index 000000000000..722afc993cb0 --- /dev/null +++ b/tools/perf/tests/shell/coresight/context_switch_loop/Makefile @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 + +include ../Makefile.miniconfig + +BIN=context_switch_loop +LIB= + +all: $(BIN) + +$(BIN): $(BIN).c +ifdef CORESIGHT +ifeq ($(ARCH),arm64) + $(Q)$(CC) $(CFLAGS) $(BIN).c -O0 -o $(BIN) $(LIB) +endif +endif + +install-tests: all +ifdef CORESIGHT +ifeq ($(ARCH),arm64) + $(call QUIET_INSTALL, tests) \ + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$(INSTDIR_SUB)/$(BIN)'; \ + $(INSTALL) $(BIN) '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/$(INSTDIR_SUB)/$(BIN)/$(BIN)' +endif +endif + +clean: + $(Q)$(RM) -f $(BIN) + +.PHONY: all clean install-tests diff --git a/tools/perf/tests/shell/coresight/context_switch_loop/context_switch_loop.c b/tools/perf/tests/shell/coresight/context_switch_loop/context_switch_loop.c new file mode 100644 index 000000000000..6b7cd11952a2 --- /dev/null +++ b/tools/perf/tests/shell/coresight/context_switch_loop/context_switch_loop.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/compiler.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <unistd.h> + +static int loops = 100; +static char buf; +int work = 1234; + +#define write_block(fd) \ + do { \ + if (write(fd, &buf, 1) <= 0) \ + exit(1); \ + } while (0) + +#define read_block(fd) \ + do { \ + if (read(fd, &buf, 1) <= 0) \ + exit(1); \ + } while (0) + +static noinline void thread1(int in_fd, int out_fd) +{ + for (int i = 0; i < loops; i++) { + read_block(in_fd); + work += i * 3; + write_block(out_fd); + } +} + +static noinline void thread2(int in_fd, int out_fd) +{ + for (int i = 0; i < loops; i++) { + write_block(out_fd); + work += i * 7; + read_block(in_fd); + } +} + +int main(int argc, char **argv) +{ + int a_to_b[2], b_to_a[2]; + pid_t thread1_pid; + int status; + + if (argc > 1) { + loops = atoi(argv[1]); + if (loops < 0) { + fprintf(stderr, "Invalid number of loops: %s\n", argv[1]); + return 1; + } + } + + if (pipe(a_to_b) || pipe(b_to_a)) { + perror("Pipe error"); + return 1; + } + + thread1_pid = fork(); + if (thread1_pid < 0) { + perror("Fork error"); + return 1; + } + + if (!thread1_pid) { + prctl(PR_SET_NAME, "thread1", 0, 0, 0); + thread1(b_to_a[0], a_to_b[1]); + exit(0); + } + + prctl(PR_SET_NAME, "thread2", 0, 0, 0); + thread2(a_to_b[0], b_to_a[1]); + + if (waitpid(thread1_pid, &status, 0) != thread1_pid || !WIFEXITED(status) || + WEXITSTATUS(status)) + return 1; + + return 0; +} diff --git a/tools/perf/tests/shell/coresight/context_switch_thread.sh b/tools/perf/tests/shell/coresight/context_switch_thread.sh new file mode 100755 index 000000000000..10b4118dff9b --- /dev/null +++ b/tools/perf/tests/shell/coresight/context_switch_thread.sh @@ -0,0 +1,48 @@ +#!/bin/bash -e +# CoreSight / Context switch thread attribution (exclusive) + +# SPDX-License-Identifier: GPL-2.0 + +TEST="context_switch_loop" + +if [ "$(id -u)" != 0 ]; then + # Requires root for "-C 0" in record command + echo "[Skip] No root permission" + exit 2 +fi + +# shellcheck source=../lib/coresight.sh +. "$(dirname $0)"/../lib/coresight.sh + +DATA="$DATD/perf-$TEST.data" +SCRIPT="$DATD/perf-$TEST.script" + +check_samples() { + owner_samples=$(grep -c "thread1.*thread1" "$SCRIPT" || true) + next_samples=$(grep -c "thread2.*thread2" "$SCRIPT" || true) + + if [ "$owner_samples" -eq 0 ] || [ "$next_samples" -eq 0 ]; then + err "No samples found" + fi + + if grep "thread2.*thread1" "$SCRIPT"; then + err "Thread1 symbol was attributed to thread2" + fi + + if grep "thread1.*thread2" "$SCRIPT"; then + err "Thread2 symbol was attributed to thread1" + fi +} + +# Pin to one CPU so the two threads alternate running but record into the +# same trace buffer. +perf record -o "$DATA" -e cs_etm/timestamp=0/u -C 0 \ + -- taskset --cpu-list 0 "$BIN" 20 > /dev/null 2>&1 + +# Test both instruction and branch sample generation modes. +perf script -i "$DATA" --itrace=i4 -F comm,pid,tid,ip,sym > "$SCRIPT" 2>/dev/null +check_samples +perf script -i "$DATA" --itrace=b -F comm,pid,tid,ip,sym > "$SCRIPT" 2>/dev/null +check_samples + +exit 0