[PATCH 2/2] perf test cs-etm: Test thread attribution
James Clark
james.clark at linaro.org
Tue May 26 04:14:37 PDT 2026
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 at 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
--
2.34.1
More information about the linux-arm-kernel
mailing list