perf test: Add Arm CoreSight callchain test

Add a CoreSight shell test for synthesized callchains.

The test uses the new callchain workload to generate trace and decodes
it with synthesis callchain. It then verifies that the instruction
samples show the expected callchain push and pop.

Use control FIFOs so tracing starts only around the workload, which
keeps the trace data small. The test is limited to with the cs_etm
event available and root permission.

After:

  perf test 138 -vvv
  138: CoreSight synthesized callchain:
  ---- start ----
  test child forked, pid 35581
  Callchain flow matched:
    l1=4642868 l2=4642880 l3=4642895 l4=4642919 l5=4670494 l6=4670500 l7=4670520
  ---- end(0) ----
  138: CoreSight synthesized callchain                                                                           : Ok

Assisted-by: Codex:GPT-5.5
Reviewed-by: James Clark <james.clark@linaro.org>
Signed-off-by: Leo Yan <leo.yan@arm.com>
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
This commit is contained in:
Leo Yan
2026-07-02 20:51:44 +01:00
committed by Namhyung Kim
parent 41a7388d07
commit ca0e19074b
6 changed files with 213 additions and 2 deletions

View File

@@ -57,7 +57,8 @@ OPTIONS
--workload=::
Run a built-in workload, to list them use '--list-workloads', current
ones include: noploop, thloop, leafloop, sqrtloop, brstack, datasym,
context_switch_loop, deterministic, named_threads and landlock.
context_switch_loop, deterministic, named_threads, landlock and
callchain.
Used with the shell script regression tests.
@@ -69,7 +70,8 @@ OPTIONS
'named_threads' accepts the number of threads and the number of loops to
do in each thread.
The datasym, landlock and deterministic workloads don't accept any.
The datasym, landlock, deterministic and callchain workloads don't accept
any.
--list-workloads::
List the available workloads to use with -w/--workload.

View File

@@ -171,6 +171,7 @@ static struct test_workload *workloads[] = {
&workload__jitdump,
&workload__context_switch_loop,
&workload__deterministic,
&workload__callchain,
#ifdef HAVE_RUST_SUPPORT
&workload__code_with_type,

View File

@@ -0,0 +1,172 @@
#!/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

View File

@@ -248,6 +248,7 @@ DECLARE_WORKLOAD(inlineloop);
DECLARE_WORKLOAD(jitdump);
DECLARE_WORKLOAD(context_switch_loop);
DECLARE_WORKLOAD(deterministic);
DECLARE_WORKLOAD(callchain);
#ifdef HAVE_RUST_SUPPORT
DECLARE_WORKLOAD(code_with_type);

View File

@@ -13,6 +13,7 @@ perf-test-y += inlineloop.o
perf-test-y += jitdump.o
perf-test-y += context_switch_loop.o
perf-test-y += deterministic.o
perf-test-y += callchain.o
ifeq ($(CONFIG_RUST_SUPPORT),y)
perf-test-y += code_with_type.o
@@ -27,3 +28,4 @@ CFLAGS_traploop.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
CFLAGS_inlineloop.o = -g -O2
CFLAGS_deterministic.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
CFLAGS_named_threads.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE
CFLAGS_callchain.o = -g -O0 -fno-inline -U_FORTIFY_SOURCE

View File

@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "../tests.h"
/*
* Mark as noinline to establish the call chain, and avoid the static
* annotation to prevent LTO from renaming the functions.
*/
noinline void callchain_do_syscall(void);
noinline void callchain_foo(void);
noinline int callchain(int argc, const char **argv);
noinline void callchain_do_syscall(void)
{
syscall(SYS_gettid);
}
noinline void callchain_foo(void)
{
callchain_do_syscall();
}
noinline int callchain(int argc __maybe_unused,
const char **argv __maybe_unused)
{
callchain_foo();
return 0;
}
DEFINE_WORKLOAD(callchain);