[PATCH 7/9] perf test cs-etm: Test branch history on existing samples
James Clark
james.clark at linaro.org
Wed Aug 12 07:13:00 PDT 2026
On 03/08/2026 10:06, Amir Ayupov wrote:
> Add a CoreSight shell test for --itrace=L. Record timestamped ETM trace
> with explicit -T sample timestamps and AUX pause/resume events, then
> check that the pause samples carry both a multi-frame callchain and a
> non-empty branch stack for each of the workload's two processes.
>
> Decode the same recording with L4 and L64 and reject any branch stack
> deeper than the requested depth.
>
> The test skips when cs_etm is absent, when not run as root, or when the
> recording turns out to lack virtual timestamps. It exercises the
> timestamp-gated path and the requested-depth bound; it does not attempt
> to verify that the attached history is correlated to the sample.
>
> Signed-off-by: Amir Ayupov <aaupov at fb.com>
> ---
> .../tests/shell/coresight/add_last_branch.sh | 175 ++++++++++++++++++
> 1 file changed, 175 insertions(+)
> create mode 100755 tools/perf/tests/shell/coresight/add_last_branch.sh
>
> diff --git a/tools/perf/tests/shell/coresight/add_last_branch.sh b/tools/perf/tests/shell/coresight/add_last_branch.sh
> new file mode 100755
> index 0000000000000..4654069ad651f
> --- /dev/null
> +++ b/tools/perf/tests/shell/coresight/add_last_branch.sh
> @@ -0,0 +1,175 @@
> +#!/bin/bash -e
> +# SPDX-License-Identifier: GPL-2.0
> +# CoreSight branch history on existing samples (exclusive)
> +
> +perf list pmu | grep -q 'cs_etm//' || exit 2
> +
> +if [ "$(id -u)" != 0 ]; then
> + echo "[Skip] No root permission"
> + exit 2
> +fi
Is this so you can use -C 0? It's not completely obvious what that has
to do with the test. Can you not drop the -C option or use --per-thread
mode with a simpler non-forking workload?
I don't mind keeping it for some variety in the tests, but it should be
documented.
> +
> +tmpdir=$(mktemp -d /tmp/perf-cs-add-last-branch.XXXXX)
> +
> +cleanup()
> +{
> + rm -rf "$tmpdir"
> + trap - EXIT TERM INT
> +}
> +
> +# shellcheck disable=SC2317 # Called through trap.
> +trap_cleanup()
> +{
> + cleanup
> + exit 1
> +}
> +trap trap_cleanup EXIT TERM INT
> +
> +record_data()
> +{
> + if perf record -T -o "$tmpdir/data" -C 0 \
> + -e cs_etm/aux-action=start-paused,timestamp/u \
Timestamp needs a value on newer kernels or Perf returns an error. But
do you need to provide the option at all? It's on by default for per-CPU
mode.
> + -e cycles/aux-action=resume,period=550019/u \
> + -e cycles/aux-action=pause,period=100003,call-graph=fp/u -- \
> + taskset --cpu-list 0 perf test -w context_switch_loop 100000 \
The other Coresight tests use --workload-ctl to record less data and
save some decode time. I think this test might benefit from it too.
> + >/dev/null 2>"$tmpdir/stderr"; then
> + return 0
> + fi
> +
> + echo "Failed to record ETM trace with AUX pause/resume" >&2
> + cat "$tmpdir/stderr" >&2
> + return 1
> +}
> +
> +decode()
> +{
> + local size=$1
> + local output=$2
> +
> + if perf script -i "$tmpdir/data" --itrace="L$size" \
> + -F comm,pid,tid,event,ip,brstack >"$output" \
> + 2>"$tmpdir/stderr"; then
> + return 0
> + fi
> +
> + if grep -q "itrace=L requires virtual timestamped trace" \
> + "$tmpdir/stderr"; then
> + echo "[Skip] Virtual CoreSight timestamps are not available"
> + cleanup
> + exit 2
> + fi
> +
> + cat "$tmpdir/stderr" >&2
> + return 1
> +}
> +
> +check_process_samples()
> +{
> + local output=$1
> + local comm
> +
> + for comm in proc1 proc2; do
> + awk -v comm="$comm" '
> + $1 == comm && /cycles\/aux-action=pause/ {
> + in_sample = 1
> + next
> + }
> + !NF {
> + in_sample = 0
> + next
> + }
> + in_sample && /0x[[:xdigit:]]+\/0x[[:xdigit:]]+\// {
> + found = 1
> + }
> + END { exit !found }
> + ' "$output" || {
> + echo "No pause-event branch stack found for $comm" >&2
> + return 1
> + }
> + done
> +}
> +
> +check_callchains()
> +{
> + local output="$tmpdir/script-callchain"
> +
> + perf script -i "$tmpdir/data" -F comm,event,ip >"$output" 2>/dev/null
> +
> + awk '
> + /cycles\/aux-action=pause/ {
> + in_sample = 1
> + frames = 0
> + next
> + }
> + !NF {
> + if (in_sample && frames >= 2)
> + found = 1
> + in_sample = 0
> + next
> + }
> + in_sample && /^[[:space:]]+[[:xdigit:]]+([[:space:]]|$)/ {
> + frames++
> + }
> + END {
> + if (in_sample && frames >= 2)
> + found = 1
> + exit !found
> + }
> + ' "$output" || {
> + echo "No multi-frame pause-event callchain found" >&2
> + return 1
> + }
Can you add some example output in the test saying what these awks are
looking for. It failed for me but I wasn't sure why. I've attached my
script-callchain file if that helps.
> +}
> +
> +check_branch_stacks()
> +{
> + local output=$1
> + local max_entries=$2
> +
> + local ret
> +
> + if awk -v max="$max_entries" '
> + /0x[[:xdigit:]]+\/0x[[:xdigit:]]+\// {
> + entries = 0
> + for (i = 1; i <= NF; i++)
> + if ($i ~ /^0x[[:xdigit:]]+\/0x[[:xdigit:]]+\//)
> + entries++
> + if (entries)
> + found = 1
> + if (entries > max) {
> + status = 2
> + exit
> + }
> + }
> + END {
> + if (status)
> + exit status
> + if (!found)
> + exit 1
> + }
> + ' "$output"; then
> + return 0
> + else
> + ret=$?
> + fi
> +
> + case $ret in
> + 1) echo "No ETM branch stacks found" >&2 ;;
> + 2) echo "Branch stack exceeds requested L$max_entries depth" >&2 ;;
> + esac
> + return 1
> +}
> +
> +record_data
> +check_callchains
> +
> +decode 4 "$tmpdir/script-l4"
> +check_process_samples "$tmpdir/script-l4"
> +check_branch_stacks "$tmpdir/script-l4" 4
> +
> +decode 64 "$tmpdir/script-l64"
> +check_process_samples "$tmpdir/script-l64"
> +check_branch_stacks "$tmpdir/script-l64" 64
> +
> +cleanup
> +exit 0
More information about the linux-arm-kernel
mailing list