* [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found
@ 2026-11-13 20:08 Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 01/11] perf scripts: Add a coverage harness for perf Arnaldo Carvalho de Melo
` (10 more replies)
0 siblings, 11 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Hi,
Please consider merging,
Nothing measured how much of perf the test suite reaches. Patch 1 adds
tools/perf/scripts/coverage.sh: it builds perf with clang source based
coverage, runs the complete perf test suite and reports line and function
coverage, optionally against a baseline, with --table and --html like
pahole's coverage_table.py. Also as 'make -C tools/perf coverage',
'coverage-prep' and 'coverage-table'.
The rest is what the first runs found, in the code, the build and the
tests:
- clang DWARF 5 describes globals with DW_OP_addrx, which was not
resolved (patch 2).
- The flexible array member had no test; one showed the struct row at
0.00% while the member said 100.00% (patches 3-4).
- A struct pointer that -O2 keeps in a register was not resolved from
its DWARF location (patch 5).
- --insn-stat was printed before the loop that accounts the samples,
so --code-with-type showed zeros (patch 6).
- The libbfd probe printed a missing bfd.h error five times per
build-test run (patch 7).
- FEATURE-DUMP didn't record llvm-perf and rust, disabling both in the
build-test builds (patch 8).
- The Intel PT virtual LBR check lost every aux sample with a single
data page (patch 9).
- The dlfilter test kept samples whose IP the kernel now zeroes on a
userspace-to-kernel skid (patch 10).
- The LBR inline unwinding check failed instead of skipping when the
AMD PMU rejects branch sampling (patch 11).
In a root run on this box the suite covered 49.16% of perf's 201,180
lines. The POLL_HUP tracepoint regression and the lock contention count
fail in code this series does not change; the LBR inline unwinding check
failed there and now skips.
The numbers above are from v1 and are slightly different here: the
cs-etm patch was dropped and the issues reported in the v1 review were
addressed, which changes both the tree and what the report counts.
The harness and the tests were developed with an AI coding assistant
(OpenCode), which drafted the script and the tests and helped debug the
failures; I reviewed every change and reproduced each failure before
fixing it.
What changed from v1:
- Patch 9 (cs-etm) is gone: Leo Yan's series replaces it.
- Patch 1: O= is accepted by all targets, the Python extension is in
the report, -f is normalized and the delta matches its baseline.
- Patch 4: the TUI browser counts flexible array accesses too.
- Patch 6: the stats test skips without libdw.
- Patches 9 and 11 (were 10 and 12): Fixes tags corrected.
Best regards,
- Arnaldo
Arnaldo Carvalho de Melo (11):
perf scripts: Add a coverage harness for perf
perf annotate-data: Resolve DW_OP_addrx global variables
perf tests: Exercise the flexible array member path
perf annotate-data: Count flexible array accesses for the whole struct
perf tests: Exercise data type resolution from a register
perf annotate: Print the stats after the annotations
perf tests: Silence the libbfd probe in build-test
perf build: Record the display-only features in FEATURE-DUMP
perf tests: Fix the Intel PT virtual LBR check
perf test: Drop samples without an IP in the dlfilter test
perf test: Skip the LBR inline unwinding check when unsupported
tools/perf/.gitignore | 1 +
tools/perf/Makefile | 11 +-
tools/perf/Makefile.config | 4 +
tools/perf/Makefile.perf | 4 +
tools/perf/builtin-annotate.c | 10 +-
tools/perf/scripts/coverage.sh | 722 ++++++++++++++++++
tools/perf/tests/make | 2 +-
tools/perf/tests/shell/addr2line_inlines.sh | 9 +-
tools/perf/tests/shell/annotate_stats.sh | 64 ++
.../perf/tests/shell/data_type_flex_array.sh | 142 ++++
.../tests/shell/data_type_profiling_clang.sh | 108 +++
tools/perf/tests/shell/data_type_register.sh | 120 +++
tools/perf/tests/shell/script_dlfilter.sh | 2 +-
tools/perf/tests/shell/test_intel_pt.sh | 9 +-
tools/perf/ui/browsers/annotate-data.c | 7 +-
tools/perf/util/annotate-data.c | 24 +-
tools/perf/util/annotate-data.h | 3 +
tools/perf/util/dwarf-aux.c | 42 +-
18 files changed, 1260 insertions(+), 24 deletions(-)
create mode 100755 tools/perf/scripts/coverage.sh
create mode 100755 tools/perf/tests/shell/annotate_stats.sh
create mode 100755 tools/perf/tests/shell/data_type_flex_array.sh
create mode 100755 tools/perf/tests/shell/data_type_profiling_clang.sh
create mode 100755 tools/perf/tests/shell/data_type_register.sh
base-commit: cc905a18a647a8e3ad42cd1596baa0e08fba26eb
v1-head: 3f5465bea8f5a97a
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 01/11] perf scripts: Add a coverage harness for perf
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 02/11] perf annotate-data: Resolve DW_OP_addrx global variables Arnaldo Carvalho de Melo
` (9 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
Build perf with clang source based coverage, run the complete perf test
suite, merge the raw profiles and report line and function coverage for
perf, optionally against a baseline. This is pahole's
coverage/coverage.sh + coverage-diff.sh merged into one script, wired as
'make -C tools/perf coverage' and listed in 'make help'.
Without -f the report lists the files with the most uncovered lines,
which is where new tests should try to get more coverage; -t narrows
the tests, -f focuses on specific files, --html writes a browsable
report and --table/-T, also available as 'make -C tools/perf
coverage-table', prints the last run as an ASCII table with the
per-file line and function coverage, like pahole's coverage_table.py.
The build can be split from the test run: 'make -C tools/perf
coverage-prep' builds the instrumented perf and records the build
directory, and a later run as root, which needs no make, checks that
build, runs the tests and renders the report, or prints the table with
--table. This is for running the tests that need root on the bare
metal host while the instrumented perf is built in the development
container.
It is a maintainer tool and it already paid for itself: the first run
found the clang DWARF 5 data type test failing (the DW_OP_addrx gap,
fixed by the next patch), and the uncovered line list is what drove the
tests in the rest of the series.
$ scripts/coverage.sh
...
Total: 67436 of 201128 lines covered (33.53%)
largest uncovered files (>= 100 lines)
file lines cover missed fn-cover
builtin-trace.c 3657 4.24% 3502 0.51%
util/intel-pt.c 3237 0.00% 3237 0.00%
util/intel-pt-decoder/intel-pt-decoder.c 3208 0.00% 3208 0.00%
builtin-sched.c 3737 19.72% 3000 11.35%
ui/browsers/hists.c 2730 0.00% 2730 0.00%
util/probe-event.c 2765 5.39% 2616 10.68%
builtin-script.c 3212 29.98% 2249 33.06%
...
Report: /tmp/build/perf-coverage/cov/report.txt
The run above was unprivileged, so tests that need root are skipped or
fail; the report still points at the gaps.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/.gitignore | 1 +
tools/perf/Makefile | 11 +-
tools/perf/Makefile.perf | 4 +
tools/perf/scripts/coverage.sh | 722 +++++++++++++++++++++++++++++++++
4 files changed, 737 insertions(+), 1 deletion(-)
create mode 100755 tools/perf/scripts/coverage.sh
diff --git a/tools/perf/.gitignore b/tools/perf/.gitignore
index 3b968c5158b822ce..9052f25597ac9489 100644
--- a/tools/perf/.gitignore
+++ b/tools/perf/.gitignore
@@ -27,6 +27,7 @@ stats-*.csv
cscope*
config.mak
config.mak.autogen
+.coverage-build-dir
*-bison.*
*-flex.*
*.pyc
diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index 00baf90e5bda7123..8df8e9ed37f0a8f5 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -140,6 +140,15 @@ check-headers:
sync-headers:
@./scripts/sync-headers.sh $(SYNC_HEADERS_ARGS)
+coverage-prep:
+ @./scripts/coverage.sh --prep $(if $(FULL_O),--build-dir=$(FULL_O)) $(COVERAGE_ARGS)
+
+coverage:
+ @./scripts/coverage.sh $(if $(FULL_O),--build-dir=$(FULL_O)) $(COVERAGE_ARGS)
+
+coverage-table:
+ @./scripts/coverage.sh --table $(if $(FULL_O),--build-dir=$(FULL_O)) $(COVERAGE_ARGS)
+
#
# All other targets get passed through:
#
@@ -147,4 +156,4 @@ sync-headers:
$(print_msg)
$(make)
-.PHONY: all clean tags TAGS FORCE Makefile build-test build-test-tarball check-headers sync-headers
+.PHONY: all clean tags TAGS FORCE Makefile build-test build-test-tarball check-headers sync-headers coverage coverage-prep coverage-table
diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf
index b74b534e1153ffcb..782929b0ba10d8f3 100644
--- a/tools/perf/Makefile.perf
+++ b/tools/perf/Makefile.perf
@@ -873,6 +873,10 @@ help:
@echo ' check-headers - check which header copies are out of sync with the kernel'
@echo ' sync-headers - sync those header copies with the kernel, creating commits'
@echo ' (SYNC_HEADERS_ARGS="..." passes options to the script)'
+ @echo ' coverage-prep - build perf with clang coverage, ready for a root test run'
+ @echo ' coverage - build perf with clang coverage, run the tests and report'
+ @echo ' coverage-table - print an ASCII table of the last coverage run'
+ @echo ' (COVERAGE_ARGS="..." passes options to the script)'
DOC_TARGETS := doc man html info pdf
diff --git a/tools/perf/scripts/coverage.sh b/tools/perf/scripts/coverage.sh
new file mode 100755
index 0000000000000000..2f248d61a733938a
--- /dev/null
+++ b/tools/perf/scripts/coverage.sh
@@ -0,0 +1,722 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2026, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
+#
+# LLVM source-based coverage for perf: build with clang instrumentation,
+# run the complete perf test suite and report line/function coverage for
+# perf, optionally comparing it against a previously saved report. -t
+# narrows the tests and -f adds files to the per-file table; without -f
+# the report lists the files with the most uncovered lines, which is
+# where new tests are needed. --table prints the last run as an ASCII
+# coverage table, like pahole's coverage_table.py. The build can be
+# split from the test run: --prep builds the instrumented perf and
+# records the build directory, and a later run as root uses that build,
+# skips the build and goes straight to the tests. The sampling based
+# tests move the percentages by a percent or two between identical runs.
+
+set -euo pipefail
+
+# awk prints dots and printf/sort parse them: keep the numeric locale fixed
+# so --table doesn't abort where the decimal separator is a comma.
+export LC_ALL=C
+
+PERF_DIR=$(cd "$(dirname "$0")/.." && pwd)
+
+DEFAULT_BUILD_DIR=/tmp/build/perf-coverage
+BUILD_DIR=${BUILD_DIR:-}
+COV_DIR=${COV_DIR:-}
+BUILD_DIR_SET=0
+COV_DIR_SET=0
+[ -n "$BUILD_DIR" ] && BUILD_DIR_SET=1
+[ -n "$COV_DIR" ] && COV_DIR_SET=1
+JOBS=${JOBS:-$(nproc 2>/dev/null || echo 1)}
+LLVM_PROFDATA=${LLVM_PROFDATA:-llvm-profdata}
+LLVM_COV=${LLVM_COV:-llvm-cov}
+
+DEBUG_BUILD=1
+SKIP_BUILD=0
+REPORT_ONLY=0
+PREP=0
+TABLE=0
+HTML=0
+USE_BASELINE=1
+BASELINE=
+UNCOVERED=${UNCOVERED:-20}
+TOP=${TOP:-20}
+MIN_LINES=${MIN_LINES:-100}
+DWARF_VERSION=${DWARF_VERSION:-4}
+TESTS=()
+
+FOCUS=()
+
+ROOT=0
+[ "$(id -u)" = 0 ] && ROOT=1
+
+say() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
+warn() { printf '\033[0;33mwarning:\033[0m %s\n' "$*" >&2; }
+die() { printf '\033[0;31merror:\033[0m %s\n' "$*" >&2; exit 1; }
+
+usage()
+{
+ cat <<EOF
+Usage: scripts/coverage.sh [options] [test name fragment...]
+
+Builds perf with clang source-based coverage, runs the complete perf
+test suite and reports coverage for perf. Use -t to run a subset and
+-f to focus the per-file table.
+
+Options:
+ -b, --build-dir DIR perf build directory (default: $DEFAULT_BUILD_DIR)
+ -c, --cov-dir DIR profiles and reports (default: BUILD_DIR/cov)
+ -j, --jobs N parallel build jobs (default: $JOBS)
+ -t, --tests "A;B" test name fragments, ';' separated (default: all)
+ -f, --focus FILE add a file to the per-file table (repeatable)
+ --top N when no -f is given, show the N files with the
+ most uncovered lines (default: $TOP, 0 to disable)
+ --baseline FILE baseline report (default: COV_DIR/baseline.txt)
+ --no-baseline do not compare against or update a baseline
+ --uncovered N uncovered line numbers per focus file (default: $UNCOVERED)
+ --skip-build reuse the existing build, just run the tests
+ --report-only reuse build and profiles, just render the report
+ --prep build the instrumented perf and stop; run the
+ tests later as root with this script
+ -T, --table print an ASCII table of the last run and exit,
+ like pahole's coverage_table.py
+ --optimized build with perf's default -O3 instead of -O0 -g
+ --dwarf-version N DWARF version for the instrumented build (default: $DWARF_VERSION)
+ --html also write an HTML report
+ -h, --help show this help
+
+Environment:
+ BUILD_DIR, COV_DIR, JOBS, LLVM_COV, LLVM_PROFDATA, UNCOVERED, TOP,
+ MIN_LINES, DWARF_VERSION
+
+As root, without --prep, the script skips the build, checks the perf
+prepared by 'make coverage-prep', runs the tests and renders the
+report. The prepared build is recorded in tools/perf/.coverage-build-dir.
+
+Through make:
+ make -C tools/perf coverage COVERAGE_ARGS="-t 'perf annotate'"
+ make -C tools/perf coverage-prep O=/tmp/build/perf-tools-next
+ make -C tools/perf coverage-table
+
+Examples:
+ scripts/coverage.sh # full suite, fresh build
+ scripts/coverage.sh --prep # build only, test later as root
+ scripts/coverage.sh --report-only # re-render the last run
+ scripts/coverage.sh --table # ASCII table of the last run
+ scripts/coverage.sh -t "perf annotate" # a single test fragment
+ scripts/coverage.sh -f util/evsel.c # add a file to the table
+EOF
+}
+
+while [ $# -gt 0 ]; do
+ arg=$1
+ optval=
+ case "$arg" in
+ --*=*) optval=${arg#*=}; arg=${arg%%=*} ;;
+ esac
+
+ case "$arg" in
+ -b|--build-dir) BUILD_DIR=${optval:-${2?missing value}}; BUILD_DIR_SET=1
+ [ -z "$optval" ] && shift ;;
+ -c|--cov-dir) COV_DIR=${optval:-${2?missing value}}; COV_DIR_SET=1
+ [ -z "$optval" ] && shift ;;
+ -j|--jobs) JOBS=${optval:-${2?missing value}}; [ -z "$optval" ] && shift ;;
+ -f|--focus) FOCUS+=("${optval:-${2?missing value}}"); [ -z "$optval" ] && shift ;;
+ --baseline) BASELINE=${optval:-${2?missing value}}; [ -z "$optval" ] && shift ;;
+ --uncovered) UNCOVERED=${optval:-${2?missing value}}; [ -z "$optval" ] && shift ;;
+ --top) TOP=${optval:-${2?missing value}}; [ -z "$optval" ] && shift ;;
+ --dwarf-version) DWARF_VERSION=${optval:-${2?missing value}}; [ -z "$optval" ] && shift ;;
+ -t|--tests) IFS=';' read -r -a _t <<< "${optval:-${2?missing value}}"
+ TESTS+=("${_t[@]}"); [ -z "$optval" ] && shift ;;
+ --no-baseline) USE_BASELINE=0 ;;
+ --skip-build) SKIP_BUILD=1 ;;
+ --report-only) REPORT_ONLY=1; SKIP_BUILD=1 ;;
+ --prep) PREP=1 ;;
+ -T|--table) TABLE=1; REPORT_ONLY=1; SKIP_BUILD=1 ;;
+ --optimized) DEBUG_BUILD=0 ;;
+ --html) HTML=1 ;;
+ -h|--help) usage; exit 0 ;;
+ -*) die "unknown option: $arg (see --help)" ;;
+ *) TESTS+=("$arg") ;;
+ esac
+ shift
+done
+
+#
+# The build directory is recorded by --prep (and by a full run) so a
+# later root run, which does not build, finds it without arguments.
+#
+if [ "$BUILD_DIR_SET" = 0 ] && [ -z "$BUILD_DIR" ] && [ "$PREP" = 0 ] &&
+ { [ "$ROOT" = 1 ] || [ "$SKIP_BUILD" = 1 ] ||
+ [ "$REPORT_ONLY" = 1 ] || [ "$TABLE" = 1 ]; } &&
+ [ -f "$PERF_DIR/.coverage-build-dir" ]; then
+ read -r BUILD_DIR < "$PERF_DIR/.coverage-build-dir"
+fi
+[ -n "$BUILD_DIR" ] || BUILD_DIR=$DEFAULT_BUILD_DIR
+
+if [ "$COV_DIR_SET" = 0 ] && [ -z "$COV_DIR" ] &&
+ [ -f "$BUILD_DIR/cov/state" ]; then
+ COV_DIR=$(awk -F= '$1 == "COV_DIR" { print $2; exit }' \
+ "$BUILD_DIR/cov/state")
+fi
+[ -n "$COV_DIR" ] || COV_DIR=$BUILD_DIR/cov
+
+# The tests run from the kernel tree root, so keep these absolute.
+abspath()
+{
+ case "$1" in
+ /*) printf '%s\n' "$1" ;;
+ *) printf '%s/%s\n' "$PWD" "$1" ;;
+ esac
+}
+BUILD_DIR=$(abspath "$BUILD_DIR")
+COV_DIR=$(abspath "$COV_DIR")
+
+[ -n "$BASELINE" ] || BASELINE=$COV_DIR/baseline.txt
+[[ $TOP =~ ^[0-9]+$ ]] || die "--top wants a number, not '$TOP'"
+[[ $MIN_LINES =~ ^[0-9]+$ ]] || die "MIN_LINES wants a number, not '$MIN_LINES'"
+
+# Root does not build: use the perf prepared by coverage-prep.
+if [ "$ROOT" = 1 ] && [ "$PREP" = 0 ] && [ "$TABLE" = 0 ]; then
+ SKIP_BUILD=1
+ say "Running as root, using the coverage build in $BUILD_DIR"
+fi
+
+BUILD_LOG=$COV_DIR/build.log
+TEST_LOG=$COV_DIR/tests.log
+REPORT=$COV_DIR/report.txt
+PROFDATA=$COV_DIR/perf.profdata
+# Discovery point for a later root or --table run, so it stays under
+# BUILD_DIR even when --cov-dir points elsewhere.
+STATE_FILE=$BUILD_DIR/cov/state
+POINTER=$PERF_DIR/.coverage-build-dir
+
+#
+# Sanity checks
+#
+[ -d "$PERF_DIR" ] || die "$PERF_DIR not found"
+
+if [ "$TABLE" = 0 ]; then
+ if [ "$PREP" = 0 ]; then
+ command -v "$LLVM_COV" > /dev/null ||
+ die "$LLVM_COV not found (set LLVM_COV)"
+ fi
+ if [ "$SKIP_BUILD" = 0 ]; then
+ command -v clang > /dev/null ||
+ die "clang not found, it is the only compiler with -fcoverage-mapping"
+ fi
+ if [ "$PREP" = 0 ] && [ "$REPORT_ONLY" = 0 ]; then
+ command -v "$LLVM_PROFDATA" > /dev/null ||
+ die "$LLVM_PROFDATA not found (set LLVM_PROFDATA)"
+ fi
+ mkdir -p "$COV_DIR"
+fi
+
+#
+# Build: clang, DEBUG=1 gives -O0 -g like pahole's coverage build, which
+# keeps the line mapping honest at the cost of slower test runs. DWARF 4
+# is the default; --dwarf-version 5 describes the workloads linked into
+# perf with DW_OP_addrx, the encoding clang uses for globals in DWARF 5.
+#
+check_coverage_build()
+{
+ [ -x "$BUILD_DIR/perf" ] ||
+ die "no $BUILD_DIR/perf, run 'make -C $PERF_DIR O=$BUILD_DIR coverage-prep' first"
+ if command -v nm > /dev/null; then
+ # No -q: grep reads all of nm's output, so pipefail does
+ # not see a SIGPIPE from an early exit.
+ nm "$BUILD_DIR/perf" 2>/dev/null |
+ grep '__llvm_profile' > /dev/null ||
+ die "$BUILD_DIR/perf was not built for coverage, run coverage-prep"
+ else
+ warn "nm not found, cannot verify that $BUILD_DIR/perf is instrumented"
+ fi
+}
+
+write_state()
+{
+ mkdir -p "$(dirname "$STATE_FILE")" ||
+ die "could not create $(dirname "$STATE_FILE")"
+ {
+ printf 'BUILD_DIR=%s\n' "$BUILD_DIR"
+ printf 'COV_DIR=%s\n' "$COV_DIR"
+ printf 'DEBUG_BUILD=%s\n' "$DEBUG_BUILD"
+ printf 'DWARF_VERSION=%s\n' "$DWARF_VERSION"
+ } > "$STATE_FILE" || die "could not write $STATE_FILE"
+
+ if ! printf '%s\n' "$BUILD_DIR" > "$POINTER" 2>/dev/null; then
+ warn "could not write $POINTER, root runs will need -b $BUILD_DIR"
+ fi
+}
+
+build_perf()
+{
+ if [ "$SKIP_BUILD" = 1 ]; then
+ check_coverage_build
+ return
+ fi
+
+ say "Building perf in $BUILD_DIR"
+ mkdir -p "$BUILD_DIR"
+ local debug=()
+ [ "$DEBUG_BUILD" = 1 ] && debug=(DEBUG=1)
+
+ if ! make -C "$PERF_DIR" O="$BUILD_DIR" CC=clang \
+ "${debug[@]+"${debug[@]}"}" \
+ EXTRA_CFLAGS="-fprofile-instr-generate -fcoverage-mapping -gdwarf-$DWARF_VERSION" \
+ -j"$JOBS" > "$BUILD_LOG" 2>&1; then
+ tail -20 "$BUILD_LOG" >&2
+ die "build failed, full log in $BUILD_LOG"
+ fi
+ tail -1 "$BUILD_LOG"
+ write_state
+}
+
+#
+# Run the selected tests with the instrumented perf in PATH: the shell
+# tests, the workloads and 'perf test' itself all come from there.
+#
+run_tests()
+{
+ if [ ${#TESTS[@]} -eq 0 ]; then
+ say "Running the complete perf test suite"
+ else
+ say "Running perf tests: ${TESTS[*]}"
+ fi
+ rm -rf "$COV_DIR/raw"
+ mkdir -p "$COV_DIR/raw"
+
+ set +e
+ # The shell tests only get discovered from the kernel tree root.
+ (cd "$PERF_DIR/../.." &&
+ PATH="$BUILD_DIR:$PATH" \
+ LLVM_PROFILE_FILE="$COV_DIR/raw/perf-%p-%m.profraw" \
+ "$BUILD_DIR/perf" test -v \
+ "${TESTS[@]+"${TESTS[@]}"}") 2>&1 | tee "$TEST_LOG"
+ test_status=${PIPESTATUS[0]}
+ set -e
+
+ # 'perf test' exits 0 even when tests fail, only the summary tells.
+ if [ "$test_status" = 0 ] && grep -qE '^Failed tests +: +[1-9]' "$TEST_LOG"; then
+ test_status=1
+ fi
+
+ grep -E '^(Passed|Skipped|Failed) .*:' "$TEST_LOG" || true
+ [ "$test_status" = 0 ] ||
+ warn "some tests failed or were skipped, coverage is still collected"
+ if [ "$test_status" != 0 ] && grep -q 'Access to performance monitoring' "$TEST_LOG"; then
+ warn "perf_event_paranoid=$(cat /proc/sys/kernel/perf_event_paranoid 2>/dev/null)" \
+ "denied events to tests recording with callchains;" \
+ "run as root or lower it for the full suite"
+ fi
+}
+
+#
+# Merge the per-process profiles and render the text report.
+#
+merge_profiles()
+{
+ local count size
+ count=$(find "$COV_DIR/raw" -name '*.profraw' | wc -l)
+ size=$(du -sh "$COV_DIR/raw" 2>/dev/null | cut -f1)
+ [ "$count" -gt 0 ] ||
+ die "no .profraw files, was perf rebuilt with -fprofile-instr-generate?"
+
+ say "Merging $count profile files ($size)"
+ "$LLVM_PROFDATA" merge -sparse "$COV_DIR"/raw/*.profraw -o "$PROFDATA" ||
+ die "llvm-profdata merge failed"
+}
+
+llvm_cov_objects()
+{
+ echo "$BUILD_DIR/perf"
+ local so
+ for so in perf.so libperf.so libperf-jvmti.so; do
+ [ -f "$BUILD_DIR/$so" ] && echo "--object=$BUILD_DIR/$so"
+ done
+ # The Python extension is named after the interpreter ABI and lives
+ # in its own directory.
+ for so in "$BUILD_DIR"/python/perf*.so; do
+ [ -f "$so" ] && echo "--object=$so"
+ done
+}
+
+render_report()
+{
+ local objects
+ mapfile -t objects < <(llvm_cov_objects)
+
+ say "Generating report"
+ "$LLVM_COV" report "${objects[@]}" --instr-profile="$PROFDATA" > "$REPORT"
+}
+
+#
+# Line coverage of a full report for one file: "lines missed cover fn%".
+# Report paths are relative to the build directory, so match the requested
+# path as a suffix: a build outside the source tree gives
+# ".../tools/perf/..." and one inside gives "tools/perf/...".
+#
+# -f takes the path as it shows in the report, or a tools/perf/ relative one.
+focus_path()
+{
+ case "$1" in
+ *tools/perf/*) printf '%s\n' "${1#*tools/perf/}" ;;
+ *) printf '%s\n' "$1" ;;
+ esac
+}
+
+file_cov()
+{
+ awk -v f="$2" '
+ function perf_relpath(p) {
+ if (p ~ /(^|\/)tools\/perf\//)
+ sub(/^.*tools\/perf\//, "", p)
+ return p
+ }
+ {
+ g = f
+ sub(/^.*tools\/perf\//, "", g)
+ rel = perf_relpath($1)
+ if (rel == f || rel == g || $1 == f) {
+ print $8, $9, $10, $7
+ exit
+ }
+ }' "$1"
+}
+
+total_cov()
+{
+ awk '$1 == "TOTAL" { print $8, $9, $10 }' "$1"
+}
+
+total_delta()
+{
+ local prev=$1 cur=$2 old cur_cov
+ [ -f "$prev" ] || return 0
+ old=$(total_cov "$prev" | awk '{print $3}')
+ cur_cov=$(total_cov "$cur" | awk '{print $3}')
+ [ -n "$old" ] && [ -n "$cur_cov" ] || return 0
+ awk -v cur="$cur_cov" -v old="$old" 'BEGIN {
+ sub(/%$/, "", cur); sub(/%$/, "", old)
+ printf "%+.2f%%", cur - old }'
+}
+
+report_delta()
+{
+ local prev=$1 cur=$2 label
+ local cur_cov old_cov delta
+ label=$(focus_path "$3")
+ cur_cov=$(file_cov "$cur" "$label" | awk '{print $3}')
+ [ -n "$cur_cov" ] || return 0
+
+ if [ -f "$prev" ]; then
+ old_cov=$(file_cov "$prev" "$label" | awk '{print $3}')
+ if [ -n "$old_cov" ]; then
+ delta=$(awk -v cur="$cur_cov" -v old="$old_cov" 'BEGIN {
+ sub(/%$/, "", cur); sub(/%$/, "", old)
+ printf "%+.2f%%", cur - old }')
+ else
+ delta="(new)"
+ fi
+ else
+ old_cov="-"
+ delta="-"
+ fi
+ printf " %-36s %8s %8s %9s\n" "$label" "${old_cov:--}" "$cur_cov" "$delta"
+}
+
+#
+# With no -f, show where the uncovered lines are: the files with the most
+# missed lines are where new tests should try to get more coverage.
+#
+print_worst_files()
+{
+ [ "$TOP" -gt 0 ] || return 0
+ local rows missed lines cover fn path
+ rows=$(awk -v min="$MIN_LINES" '
+ $1 ~ /(^|\/)tools\/perf\// && $8 >= min && $9 > 0 {
+ path = $1
+ sub(/^.*tools\/perf\//, "", path)
+ printf "%s %s %s %s %s\n", $9, $8, $10, $7, path
+ }' "$REPORT" | sort -k1,1nr -k2,2nr | sed -n "1,${TOP}p")
+ [ -n "$rows" ] || return 0
+
+ printf " largest uncovered files (>= %s lines)\n" "$MIN_LINES"
+ printf " %-44s %8s %8s %8s %8s\n" \
+ "file" "lines" "cover" "missed" "fn-cover"
+ while read -r missed lines cover fn path; do
+ printf " %-44s %8s %8s %8s %8s\n" \
+ "$path" "$lines" "$cover" "$missed" "$fn"
+ done <<< "$rows"
+}
+
+print_summary()
+{
+ local lines missed cover delta file cov
+ read -r lines missed cover < <(total_cov "$REPORT")
+ printf "\n\033[1mTotal:\033[0m %s of %s lines covered (%s)" \
+ "$((lines - missed))" "$lines" "$cover"
+ if [ "$USE_BASELINE" = 1 ] && [ -f "$BASELINE" ] &&
+ ! cmp -s "$BASELINE" "$REPORT"; then
+ delta=$(total_delta "$BASELINE" "$REPORT")
+ [ -n "$delta" ] && printf " (previous %s, %s)" \
+ "$(total_cov "$BASELINE" | awk '{print $3}')" "$delta"
+ fi
+ printf "\n\n"
+
+ if [ ${#FOCUS[@]} -eq 0 ]; then
+ print_worst_files
+ return
+ fi
+
+ printf " \033[1m%-36s\033[0m %8s %6s %6s %8s\n" \
+ "file" "lines" "cover" "missed" "fn-cover"
+ for file in "${FOCUS[@]}"; do
+ file=$(focus_path "$file")
+ cov=$(file_cov "$REPORT" "$file")
+ if [ -z "$cov" ]; then
+ printf " %-36s %8s\n" "$file" "not covered"
+ continue
+ fi
+ printf " %-36s %8s %6s %6s %8s\n" "$file" \
+ "$(echo "$cov" | awk '{print $1}')" \
+ "$(echo "$cov" | awk '{print $3}')" \
+ "$(echo "$cov" | awk '{print $2}')" \
+ "$(echo "$cov" | awk '{print $4}')"
+ done
+
+ if [ "$USE_BASELINE" = 1 ] && [ -f "$BASELINE" ] &&
+ ! cmp -s "$BASELINE" "$REPORT"; then
+ printf "\n per file delta since %s\n" "$BASELINE"
+ printf " %-36s %8s %8s %9s\n" "file" "previous" "current" "delta"
+ for file in "${FOCUS[@]}"; do
+ report_delta "$BASELINE" "$REPORT" "$file"
+ done
+ fi
+}
+
+#
+# --table: render the report of the last run as an ASCII table, like
+# pahole's coverage_table.py. Without -f the rows are the TOP files with
+# the most uncovered lines, sorted by line coverage; --top 0 lists every
+# file with at least MIN_LINES lines and uncovered lines. The total is
+# the whole report, which also counts the libraries perf links with.
+#
+bar()
+{
+ local pct=$1 width=$2 filled i out=
+ filled=$(awk -v p="$pct" -v w="$width" 'BEGIN { printf "%d", p * w / 100 }')
+ for ((i = 0; i < filled; i++)); do out+=█; done
+ for ((i = filled; i < width; i++)); do out+=░; done
+ printf '%s' "$out"
+}
+
+center()
+{
+ local left=$(( ($2 - ${#1}) / 2 ))
+ printf '%*s%s%*s' "$left" '' "$1" "$(( $2 - ${#1} - left ))" ''
+}
+
+dashes()
+{
+ local s
+ printf -v s '%*s' "$1" ''
+ printf '%s' "${s// /─}"
+}
+
+table_row()
+{
+ awk -v f="$2" '
+ function perf_relpath(p) {
+ if (p ~ /(^|\/)tools\/perf\//)
+ sub(/^.*tools\/perf\//, "", p)
+ return p
+ }
+ {
+ g = f
+ sub(/^.*tools\/perf\//, "", g)
+ path = perf_relpath($1)
+ if (path == f || path == g || $1 == f) {
+ printf "%s\t%s\t%d\t%d\t%.2f\t%s\t%d\t%s\n",
+ path, $8, $8 - $9, $9, $10, $5, $5 - $6, $7
+ exit
+ }
+ }' "$1"
+}
+
+table_rows()
+{
+ if [ ${#FOCUS[@]} -gt 0 ]; then
+ local file
+ for file in "${FOCUS[@]}"; do
+ table_row "$REPORT" "$file"
+ done | sort -t$'\t' -k5,5gr
+ return
+ fi
+
+ awk -v min="$MIN_LINES" '
+ $1 ~ /(^|\/)tools\/perf\// && $8 >= min && $9 > 0 {
+ path = $1
+ sub(/^.*tools\/perf\//, "", path)
+ printf "%s\t%s\t%d\t%d\t%.2f\t%s\t%d\t%s\n",
+ path, $8, $8 - $9, $9, $10, $5, $5 - $6, $7
+ }' "$REPORT" |
+ sort -t$'\t' -k4,4nr |
+ if [ "$TOP" -gt 0 ]; then sed -n "1,${TOP}p"; else cat; fi |
+ sort -t$'\t' -k5,5gr
+}
+
+print_table()
+{
+ local rows total path lines hit missed pct funcs funcs_hit func_cov
+ local row_fmt w d_file d29 d13 d10 d8
+
+ rows=$(table_rows)
+ [ -n "$rows" ] ||
+ die "no coverage data in $REPORT for the requested files"
+ total=$(awk '$1 == "TOTAL" {
+ printf "%s\t%d\t%d\t%.2f\t%s\t%d\t%s\n",
+ $8, $8 - $9, $9, $10, $5, $5 - $6, $7
+ }' "$REPORT")
+
+ w=4
+ while IFS=$'\t' read -r path _; do
+ if [ ${#path} -gt "$w" ]; then
+ w=${#path}
+ fi
+ done <<< "$rows"
+
+ d8=$(dashes 8)
+ d10=$(dashes 10)
+ d13=$(dashes 13)
+ d29=$(dashes 29)
+ d_file=$(dashes $((w + 2)))
+
+ printf '┌%s┬%s┬%s┬%s┬%s┬%s┬%s┐\n' \
+ "$d_file" "$d8" "$d8" "$d8" "$d29" "$d13" "$d10"
+ printf '│ %-*s │ %6s │ %6s │ %6s │ %s │ %11s │ %8s │\n' \
+ "$w" "File" "Lines" "Hit" "Miss" \
+ "$(center 'Line Coverage' 27)" "Funcs" "FuncCov"
+ printf '├%s┼%s┼%s┼%s┼%s┼%s┼%s┤\n' \
+ "$d_file" "$d8" "$d8" "$d8" "$d29" "$d13" "$d10"
+
+ row_fmt=$(printf '│ %%-%ds │ %%6s │ %%6s │ %%6s │ %%s %%5.1f%%%%' "$w")
+ row_fmt+=' │ %5s/%-5s │ %8s │\n'
+
+ while IFS=$'\t' read -r path lines hit missed pct funcs funcs_hit func_cov; do
+ printf "$row_fmt" "$path" "$lines" "$hit" "$missed" \
+ "$(bar "$pct" 20)" "$pct" "$funcs_hit" "$funcs" "$func_cov"
+ done <<< "$rows"
+
+ printf '├%s┼%s┼%s┼%s┼%s┼%s┼%s┤\n' \
+ "$d_file" "$d8" "$d8" "$d8" "$d29" "$d13" "$d10"
+ while IFS=$'\t' read -r lines hit missed pct funcs funcs_hit func_cov; do
+ printf "$row_fmt" "TOTAL" "$lines" "$hit" "$missed" \
+ "$(bar "$pct" 20)" "$pct" "$funcs_hit" "$funcs" "$func_cov"
+ done <<< "$total"
+ printf '└%s┴%s┴%s┴%s┴%s┴%s┴%s┘\n' \
+ "$d_file" "$d8" "$d8" "$d8" "$d29" "$d13" "$d10"
+}
+
+print_uncovered()
+{
+ [ "$UNCOVERED" -gt 0 ] || return 0
+ [ ${#FOCUS[@]} -gt 0 ] || return 0
+ local objects file out lines header=0
+ mapfile -t objects < <(llvm_cov_objects)
+
+ for file in "${FOCUS[@]}"; do
+ file=$(focus_path "$file")
+ # No report row means the file was not built: print_summary()
+ # already said so, llvm-cov show would dump the whole profile.
+ [ -n "$(file_cov "$REPORT" "$file")" ] || continue
+ out=$("$LLVM_COV" show "${objects[@]}" --instr-profile="$PROFDATA" \
+ "$PERF_DIR/$file" 2>/dev/null |
+ awk -F'|' '$2 ~ /^[[:space:]]*0[[:space:]]*$/ {
+ gsub(/[[:space:]]/, "", $1); printf "%s ", $1 }')
+ lines=$(echo "$out" | wc -w)
+ [ "$lines" -gt 0 ] || continue
+ if [ "$header" = 0 ]; then
+ printf "\n uncovered lines (first %s per file)\n" "$UNCOVERED"
+ header=1
+ fi
+ printf " %-24s %4s lines: %s\n" "$file" "$lines" \
+ "$(echo "$out" | cut -d' ' -f1-"$UNCOVERED")"
+ done
+}
+
+save_html()
+{
+ [ "$HTML" = 1 ] || return 0
+ local objects
+ mapfile -t objects < <(llvm_cov_objects)
+ say "Writing HTML report to $COV_DIR/html"
+ "$LLVM_COV" show "${objects[@]}" --instr-profile="$PROFDATA" \
+ --format=html --output-dir="$COV_DIR/html" \
+ --show-line-counts-or-regions --show-branches=count
+}
+
+check_stale()
+{
+ [ "$REPORT_ONLY" = 1 ] || return 0
+ if [ ! -f "$PROFDATA" ]; then
+ [ "$TABLE" = 1 ] && return 0
+ die "no $PROFDATA, run without --report-only first"
+ fi
+ local newer
+ newer=$(find "$PERF_DIR" -name '*.c' -newer "$PROFDATA" -print -quit 2>/dev/null)
+ if [ -n "$newer" ]; then
+ warn "profiles are older than source changes (e.g. $newer), results may be stale"
+ fi
+}
+
+update_baseline()
+{
+ [ "$USE_BASELINE" = 1 ] || return 0
+ [ "$REPORT_ONLY" = 1 ] && return 0
+ [ -f "$BASELINE" ] && mv "$BASELINE" "$BASELINE.prev"
+ cp "$REPORT" "$BASELINE"
+ printf "\nBaseline saved: %s\n" "$BASELINE"
+}
+
+test_status=0
+
+if [ "$TABLE" = 1 ]; then
+ [ -f "$REPORT" ] || die "no $REPORT, run coverage first"
+ check_stale
+ print_table
+ exit 0
+fi
+
+if [ "$PREP" = 1 ]; then
+ build_perf
+ write_state
+ say "Coverage build ready: $BUILD_DIR/perf"
+ printf "As root: %s/scripts/coverage.sh\n" "$PERF_DIR"
+ exit 0
+fi
+
+if [ "$REPORT_ONLY" = 0 ]; then
+ build_perf
+ run_tests
+ merge_profiles
+else
+ check_coverage_build
+ check_stale
+fi
+
+[ -f "$PROFDATA" ] || die "no $PROFDATA, run without --report-only first"
+
+render_report
+print_summary
+print_uncovered
+save_html
+update_baseline
+
+printf "\nReport: %s\n" "$REPORT"
+[ "$HTML" = 1 ] && printf "HTML: %s\n" "$COV_DIR/html/index.html"
+
+exit "$test_status"
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 02/11] perf annotate-data: Resolve DW_OP_addrx global variables
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 01/11] perf scripts: Add a coverage harness for perf Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 03/11] perf tests: Exercise the flexible array member path Arnaldo Carvalho de Melo
` (8 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
clang emits DW_AT_location with DW_OP_addrx for global variables in
DWARF 5, gcc emits DW_OP_addr. The address index was not resolved, so
those variables were not found by address; the by-name fallback also
missed the ones in .bss past the file backed end of the last segment.
Resolve the index with dwarf_getlocation_attr() + dwarf_formaddr() and
use the resulting address in the by-address lookup and in the global
variable cache. Add a test that compiles the workload with clang
-gdwarf-5 and checks that the global is annotated.
Fixes: d60469d7c0e5c4e8 ("perf dwarf-aux: Add die_find_variable_by_addr()")
Fixes: d7b60803a790b716 ("perf dwarf-aux: Add die_collect_global_vars()")
Cc: Namhyung Kim <namhyung@kernel.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
.../tests/shell/data_type_profiling_clang.sh | 108 ++++++++++++++++++
tools/perf/util/dwarf-aux.c | 42 ++++++-
2 files changed, 145 insertions(+), 5 deletions(-)
create mode 100755 tools/perf/tests/shell/data_type_profiling_clang.sh
diff --git a/tools/perf/tests/shell/data_type_profiling_clang.sh b/tools/perf/tests/shell/data_type_profiling_clang.sh
new file mode 100755
index 0000000000000000..8e425952d4742a2c
--- /dev/null
+++ b/tools/perf/tests/shell/data_type_profiling_clang.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# perf data type profiling tests with clang DWARF 5
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+# clang describes the location of globals with DW_OP_addrx where gcc
+# uses DW_OP_addr, so the workload has to be built here to get that
+# encoding.
+
+command -v clang > /dev/null || exit 2
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+perfout=$(mktemp /tmp/__perf_test.perf.out.XXXXX)
+prog=$(mktemp /tmp/__perf_test.clang.XXXXX)
+src="${prog}.c"
+
+cleanup()
+{
+ rm -f "$perfdata" "$perfout" "$prog" "$src"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup()
+{
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+# No point in recording if the PMU cannot do memory events.
+if perf mem record -o /dev/null -- true 2>&1 | \
+ grep -q "failed: no PMU supports the memory events"; then
+ cleanup
+ exit 2
+fi
+if ! perf mem record -o /dev/null -- true 2>/dev/null; then
+ echo "Skip: cannot record memory events on this PMU"
+ cleanup
+ exit 2
+fi
+
+cat > "$src" <<'EOF'
+#include <signal.h>
+#include <unistd.h>
+
+struct conn {
+ int saddr;
+ int daddr;
+ long bytes;
+};
+
+static volatile struct conn conn;
+static volatile sig_atomic_t done;
+
+static void stop(int sig)
+{
+ done = 1;
+}
+
+int main(void)
+{
+ unsigned long sum = 0;
+ int i = 0;
+
+ signal(SIGALRM, stop);
+ alarm(1);
+
+ while (!done) {
+ conn.saddr = i;
+ conn.daddr = i + 1;
+ sum += conn.bytes;
+ i++;
+ }
+ return sum != 0;
+}
+EOF
+
+# -fPIE keeps the accesses PC-relative on x86_64, the operand form the
+# data type resolver handles.
+if ! clang -O0 -gdwarf-5 -fPIE -pie -o "$prog" "$src"; then
+ echo "Skip: cannot build the workload with clang"
+ cleanup
+ exit 2
+fi
+
+if ! perf mem record -o "$perfdata" -- "$prog" > /dev/null 2>&1; then
+ echo "FAIL: perf mem record"
+ cleanup
+ exit 1
+fi
+
+if ! perf annotate --code-with-type -i "$perfdata" --stdio \
+ --percent-limit 1 > "$perfout" 2>/dev/null; then
+ echo "FAIL: perf annotate"
+ cleanup
+ exit 1
+fi
+
+if ! grep -q "# data-type: struct conn" "$perfout"; then
+ echo "FAIL: struct conn not resolved, DW_OP_addrx not handled?"
+ cleanup
+ exit 1
+fi
+
+echo "DW_OP_addrx global resolved"
+cleanup
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
index 48a11c95a67686e4..5db7458f01116a4e 100644
--- a/tools/perf/util/dwarf-aux.c
+++ b/tools/perf/util/dwarf-aux.c
@@ -14,6 +14,11 @@
#include "strbuf.h"
#include "string2.h"
+/* DWARF 5 address index, libdw's dwarf_getlocations() returns it unresolved */
+#ifndef DW_OP_addrx
+#define DW_OP_addrx 0xa1
+#endif
+
/**
* cu_find_realpath - Find the realpath of the target file
* @cu_die: A DIE(dwarf information entry) of CU(compilation Unit)
@@ -1629,6 +1634,31 @@ Dwarf_Die *die_find_variable_by_reg(Dwarf_Die *sc_die, Dwarf_Addr pc, int reg,
return result;
}
+/*
+ * Get the absolute address of a location expression that consists of a
+ * single address, either DW_OP_addr or DW_OP_addrx. libdw leaves the
+ * DWARF 5 address index unresolved, it has to be looked up in .debug_addr.
+ */
+static bool die_get_location_addr(Dwarf_Attribute *attr, Dwarf_Op *ops,
+ Dwarf_Addr *addr)
+{
+ if (ops->atom == DW_OP_addr) {
+ *addr = ops->number;
+ return true;
+ }
+
+ if (ops->atom == DW_OP_addrx) {
+ Dwarf_Attribute addr_attr;
+
+ if (dwarf_getlocation_attr(attr, ops, &addr_attr) != 0)
+ return false;
+
+ return dwarf_formaddr(&addr_attr, addr) == 0;
+ }
+
+ return false;
+}
+
/* Only checks direct child DIEs in the given scope */
static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg)
{
@@ -1647,11 +1677,13 @@ static int __die_find_var_addr_cb(Dwarf_Die *die_mem, void *arg)
return DIE_FIND_CB_SIBLING;
while ((off = dwarf_getlocations(&attr, off, &base, &start, &end, &ops, &nops)) > 0) {
- if (ops->atom != DW_OP_addr)
+ Dwarf_Addr addr;
+
+ if (!die_get_location_addr(&attr, ops, &addr))
continue;
if (check_allowed_ops(ops, nops) &&
- match_var_offset(die_mem, data, data->addr, ops->number,
+ match_var_offset(die_mem, data, data->addr, addr,
/*is_pointer=*/false))
return DIE_FIND_CB_END;
}
@@ -1777,7 +1809,7 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg)
Dwarf_Die type_die;
int tag = dwarf_tag(die_mem);
Dwarf_Attribute attr;
- Dwarf_Addr base, start, end;
+ Dwarf_Addr base, start, end, addr;
Dwarf_Op *ops;
size_t nops;
struct die_var_type *vt;
@@ -1792,7 +1824,7 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg)
if (dwarf_getlocations(&attr, 0, &base, &start, &end, &ops, &nops) <= 0)
return DIE_FIND_CB_SIBLING;
- if (ops->atom != DW_OP_addr)
+ if (!die_get_location_addr(&attr, ops, &addr))
return DIE_FIND_CB_SIBLING;
if (!check_allowed_ops(ops, nops))
@@ -1808,7 +1840,7 @@ static int __die_collect_global_vars_cb(Dwarf_Die *die_mem, void *arg)
vt->die_off = dwarf_dieoffset(&type_die);
vt->die_tag = dwarf_tag(&type_die);
vt->from_alt = !die_same_file(die_mem, &type_die);
- vt->addr = ops->number;
+ vt->addr = addr;
vt->end = 0;
vt->has_range = false;
vt->reg = -1;
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 03/11] perf tests: Exercise the flexible array member path
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 01/11] perf scripts: Add a coverage harness for perf Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 02/11] perf annotate-data: Resolve DW_OP_addrx global variables Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 04/11] perf annotate-data: Count flexible array accesses for the whole struct Arnaldo Carvalho de Melo
` (7 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
No test covered find_flex_array() and get_flex_array_member(). Add one
that keeps a struct with a trailing data[] behind a global pointer and
checks that accesses past the struct size are resolved and named after
the flexible array member.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
.../perf/tests/shell/data_type_flex_array.sh | 104 ++++++++++++++++++
1 file changed, 104 insertions(+)
create mode 100755 tools/perf/tests/shell/data_type_flex_array.sh
diff --git a/tools/perf/tests/shell/data_type_flex_array.sh b/tools/perf/tests/shell/data_type_flex_array.sh
new file mode 100755
index 0000000000000000..983cde2b3c7d2b29
--- /dev/null
+++ b/tools/perf/tests/shell/data_type_flex_array.sh
@@ -0,0 +1,104 @@
+#!/bin/bash
+# perf data type profiling tests for flexible array members
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+perfout=$(mktemp /tmp/__perf_test.perf.out.XXXXX)
+prog=$(mktemp /tmp/__perf_test.flex.XXXXX)
+src="${prog}.c"
+
+cleanup()
+{
+ rm -f "$perfdata" "$perfout" "$prog" "$src"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup()
+{
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+# No point in recording if the PMU cannot do memory events.
+if perf mem record -o /dev/null -- true 2>&1 | \
+ grep -q "failed: no PMU supports the memory events"; then
+ cleanup
+ exit 2
+fi
+if ! perf mem record -o /dev/null -- true 2>/dev/null; then
+ echo "Skip: cannot record memory events on this PMU"
+ cleanup
+ exit 2
+fi
+
+cat > "$src" <<'EOF'
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+struct flex {
+ int len;
+ char data[];
+};
+
+static struct flex *gp;
+static volatile sig_atomic_t done;
+
+static void stop(int sig)
+{
+ done = 1;
+}
+
+int main(void)
+{
+ unsigned long sum = 0;
+
+ gp = calloc(1, sizeof(*gp) + 4096);
+ if (gp == NULL)
+ return 1;
+ gp->len = 4096;
+
+ signal(SIGALRM, stop);
+ alarm(1);
+
+ while (!done) {
+ sum += gp->data[3];
+ gp->data[5] = 1;
+ }
+ return sum != 0;
+}
+EOF
+
+# -fPIE keeps the global pointer load PC-relative on x86_64, so the
+# resolver can tell the register holds a struct flex pointer.
+if ! cc -O0 -g -fPIE -pie -o "$prog" "$src" 2>/dev/null; then
+ echo "Skip: cannot build the workload"
+ cleanup
+ exit 2
+fi
+
+if ! perf mem record -o "$perfdata" -- "$prog" > /dev/null 2>&1; then
+ echo "FAIL: perf mem record"
+ cleanup
+ exit 1
+fi
+
+if ! perf annotate --code-with-type -i "$perfdata" --stdio \
+ --percent-limit 1 > "$perfout" 2>/dev/null; then
+ echo "FAIL: perf annotate"
+ cleanup
+ exit 1
+fi
+
+if ! grep -Fq "# data-type: struct flex +0x4 (data)" "$perfout"; then
+ echo "FAIL: flexible array member not resolved"
+ cleanup
+ exit 1
+fi
+
+echo "flexible array member resolved"
+cleanup
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 04/11] perf annotate-data: Count flexible array accesses for the whole struct
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
` (2 preceding siblings ...)
2026-11-13 20:08 ` [PATCH v2 03/11] perf tests: Exercise the flexible array member path Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 05/11] perf tests: Exercise data type resolution from a register Arnaldo Carvalho de Melo
` (6 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The per-member percentage sums the histogram entries in the member's
own [offset, offset + size) range. A flexible array member is resolved
past the end of its struct, so the struct row only covered its declared
size and showed 0.00% while the member row showed 100.00% and the
header said N samples.
Take a member's extent as the end of its last child as well, so the
struct counts what was resolved past it. Union members all start at
offset 0, so a plain union keeps its size; a member that is itself a
struct with a flexible array extends the union too. The TUI browser
accounts the members the same way, use the extent there as well.
Extend tests/shell/data_type_flex_array.sh to check that the accesses
count for the struct in the percent, samples and period columns.
Fixes: aaca646915a1bb53 ("perf annotate-data: Adjust type offset for flex-array")
Cc: Namhyung Kim <namhyung@kernel.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
.../perf/tests/shell/data_type_flex_array.sh | 40 ++++++++++++++++++-
tools/perf/ui/browsers/annotate-data.c | 7 ++--
tools/perf/util/annotate-data.c | 24 ++++++++++-
tools/perf/util/annotate-data.h | 3 ++
4 files changed, 67 insertions(+), 7 deletions(-)
diff --git a/tools/perf/tests/shell/data_type_flex_array.sh b/tools/perf/tests/shell/data_type_flex_array.sh
index 983cde2b3c7d2b29..7a7a782320afee23 100755
--- a/tools/perf/tests/shell/data_type_flex_array.sh
+++ b/tools/perf/tests/shell/data_type_flex_array.sh
@@ -100,5 +100,43 @@ if ! grep -Fq "# data-type: struct flex +0x4 (data)" "$perfout"; then
exit 1
fi
-echo "flexible array member resolved"
+# The accesses to the flexible array are resolved past the end of the
+# struct: they must count for the struct, not only for the member.
+if ! perf annotate --data-type -i "$perfdata" --stdio > "$perfout" 2>/dev/null; then
+ echo "FAIL: perf annotate --data-type"
+ cleanup
+ exit 1
+fi
+
+if ! awk '
+ $1 + 0 == 100.00 && /struct flex/ { struct_ok = 1 }
+ $1 + 0 == 100.00 && /char\[\]/ && /data;/ { data_ok = 1 }
+ $1 + 0 == 0.00 && /int/ && /len;/ { len_ok = 1 }
+ END { exit !(struct_ok && data_ok && len_ok) }
+' <(sed -n "/^Annotate type: 'struct flex' in/,/^Annotate type: /p" "$perfout"); then
+ echo "FAIL: --data-type does not count the flexible array accesses"
+ cleanup
+ exit 1
+fi
+
+# Same with the samples and period columns, the other two display modes.
+for mode in --show-nr-samples --show-total-period; do
+ if ! perf annotate --data-type -i "$perfdata" --stdio "$mode" > "$perfout" 2>/dev/null; then
+ echo "FAIL: perf annotate --data-type $mode"
+ cleanup
+ exit 1
+ fi
+
+ if ! awk '
+ $1 + 0 > 0 && /struct flex/ { total = $1 + 0 }
+ /char\[\]/ && /data;/ { data = $1 + 0 }
+ END { exit !(total > 0 && total == data) }
+ ' <(sed -n "/^Annotate type: 'struct flex' in/,/^Annotate type: /p" "$perfout"); then
+ echo "FAIL: --data-type $mode does not count the flexible array accesses"
+ cleanup
+ exit 1
+ fi
+done
+
+echo "flexible array member resolved and counted"
cleanup
diff --git a/tools/perf/ui/browsers/annotate-data.c b/tools/perf/ui/browsers/annotate-data.c
index 0f778788df4a934b..0e1e06acf2b231a7 100644
--- a/tools/perf/ui/browsers/annotate-data.c
+++ b/tools/perf/ui/browsers/annotate-data.c
@@ -53,12 +53,11 @@ static int get_member_overhead(struct annotated_data_type *adt,
struct evsel *leader)
{
struct annotated_member *member = entry->data;
- int i, k;
+ int i, k, end = annotated_member_extent(member);
- for (i = 0; i < member->size; i++) {
+ for (i = member->offset; i < end; i++) {
struct type_hist *h;
struct evsel *evsel;
- int offset = member->offset + i;
k = 0;
for_each_group_evsel(evsel, leader) {
@@ -69,7 +68,7 @@ static int get_member_overhead(struct annotated_data_type *adt,
continue;
h = &adt->histograms[evsel->core.idx];
- if (hashmap__find(&h->samples, offset, &hist))
+ if (hashmap__find(&h->samples, i, &hist))
update_hist_entry(&entry->hists[k], hist);
k++;
}
diff --git a/tools/perf/util/annotate-data.c b/tools/perf/util/annotate-data.c
index dd9c1d7b2da8be8a..75a0906b4df0b9e2 100644
--- a/tools/perf/util/annotate-data.c
+++ b/tools/perf/util/annotate-data.c
@@ -2233,6 +2233,25 @@ static void print_annotated_data_value(struct type_hist *h, u64 period, int nr_s
color_fprintf(stdout, color, " %7.2f", percent);
}
+/*
+ * A flexible array member is resolved past the end of its parent, so the
+ * parent's samples are not bounded by its size.
+ */
+int annotated_member_extent(struct annotated_member *member)
+{
+ struct annotated_member *child;
+ int end = member->offset + member->size;
+
+ list_for_each_entry(child, &member->children, node) {
+ int child_end = annotated_member_extent(child);
+
+ if (child_end > end)
+ end = child_end;
+ }
+
+ return end;
+}
+
static void print_annotated_data_type(struct annotated_data_type *mem_type,
struct annotated_member *member,
struct evsel *evsel, int indent)
@@ -2240,6 +2259,7 @@ static void print_annotated_data_type(struct annotated_data_type *mem_type,
struct annotated_member *child;
struct type_hist *h;
int i, nr_events = 0, samples = 0;
+ int end = annotated_member_extent(member);
u64 period = 0;
int width = symbol_conf.show_total_period ? 11 : 7;
struct evsel *pos;
@@ -2253,10 +2273,10 @@ static void print_annotated_data_type(struct annotated_data_type *mem_type,
samples = 0;
period = 0;
- for (i = 0; i < member->size; i++) {
+ for (i = member->offset; i < end; i++) {
struct type_hist_entry *entry;
- if (!hashmap__find(&h->samples, member->offset + i, &entry))
+ if (!hashmap__find(&h->samples, i, &entry))
continue;
samples += entry->nr_samples;
diff --git a/tools/perf/util/annotate-data.h b/tools/perf/util/annotate-data.h
index 184262c7356213fa..880b11849a9329c7 100644
--- a/tools/perf/util/annotate-data.h
+++ b/tools/perf/util/annotate-data.h
@@ -260,6 +260,9 @@ void global_var_type__tree_delete(struct rb_root *root);
/* Print data type annotation (including members) on stdout */
int hist_entry__annotate_data_tty(struct hist_entry *he, struct evsel *evsel);
+/* End offset of a member, including a flexible array's children */
+int annotated_member_extent(struct annotated_member *member);
+
/* Get name of member field at the given offset in the data type */
int annotated_data_type__get_member_name(struct annotated_data_type *adt,
char *buf, size_t sz, int member_offset);
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 05/11] perf tests: Exercise data type resolution from a register
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
` (3 preceding siblings ...)
2026-11-13 20:08 ` [PATCH v2 04/11] perf annotate-data: Count flexible array accesses for the whole struct Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 06/11] perf annotate: Print the stats after the annotations Arnaldo Carvalho de Melo
` (5 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
At -O2 a struct pointer lives in a register and DWARF describes it with
a register location, while the unoptimized workloads the data type tests
profile keep their variables on the stack.
Build a workload with a noinline function that gets the pointer from a
volatile global, which keeps it in a register, and check that its three
member accesses are resolved from that location.
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/tests/shell/data_type_register.sh | 120 +++++++++++++++++++
1 file changed, 120 insertions(+)
create mode 100755 tools/perf/tests/shell/data_type_register.sh
diff --git a/tools/perf/tests/shell/data_type_register.sh b/tools/perf/tests/shell/data_type_register.sh
new file mode 100755
index 0000000000000000..e0369f076ae6f4b2
--- /dev/null
+++ b/tools/perf/tests/shell/data_type_register.sh
@@ -0,0 +1,120 @@
+#!/bin/bash
+# perf data type profiling tests for register variables
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+perfout=$(mktemp /tmp/__perf_test.perf.out.XXXXX)
+prog=$(mktemp /tmp/__perf_test.regs.XXXXX)
+src="${prog}.c"
+
+cleanup()
+{
+ rm -f "$perfdata" "$perfout" "$prog" "$src"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup()
+{
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+if ! perf check feature -q dwarf; then
+ echo "Skip: no DWARF (libdw) support"
+ cleanup
+ exit 2
+fi
+
+# No point in recording if the PMU cannot do memory events.
+if perf mem record -o /dev/null -- true 2>&1 | \
+ grep -q "failed: no PMU supports the memory events"; then
+ cleanup
+ exit 2
+fi
+if ! perf mem record -o /dev/null -- true 2>/dev/null; then
+ echo "Skip: cannot record memory events on this PMU"
+ cleanup
+ exit 2
+fi
+
+cat > "$src" <<'EOF'
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+struct node {
+ long a;
+ long b;
+ long c;
+};
+
+static struct node n;
+static struct node *volatile vp;
+static volatile sig_atomic_t done;
+
+static void stop(int sig)
+{
+ done = 1;
+}
+
+__attribute__((noinline)) static long touch(struct node *p)
+{
+ long sum = 0;
+
+ sum += p->a;
+ p->b = sum;
+ sum += p->c;
+ return sum;
+}
+
+int main(void)
+{
+ unsigned long sum = 0;
+
+ vp = &n;
+ signal(SIGALRM, stop);
+ alarm(1);
+
+ while (!done)
+ sum += touch(vp);
+
+ return sum != 0;
+}
+EOF
+
+# -O2: the pointer stays in a register, so the accesses are resolved
+# from its type state instead of from a stack slot.
+if ! cc -O2 -g -fPIE -pie -o "$prog" "$src" 2>/dev/null; then
+ echo "Skip: cannot build the workload"
+ cleanup
+ exit 2
+fi
+
+if ! perf mem record -o "$perfdata" -- "$prog" > /dev/null 2>&1; then
+ echo "FAIL: perf mem record"
+ cleanup
+ exit 1
+fi
+
+if ! perf annotate --code-with-type -i "$perfdata" --stdio \
+ --percent-limit 1 > "$perfout" 2>/dev/null; then
+ echo "FAIL: perf annotate"
+ cleanup
+ exit 1
+fi
+
+for member in "+0 (a)" "+0x8 (b)" "+0x10 (c)"; do
+ if ! grep -Fq "# data-type: struct node ${member}" "$perfout"; then
+ echo "FAIL: member ${member} not resolved from the register"
+ cleanup
+ exit 1
+ fi
+done
+
+echo "register held access resolved"
+
+cleanup
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 06/11] perf annotate: Print the stats after the annotations
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
` (4 preceding siblings ...)
2026-11-13 20:08 ` [PATCH v2 05/11] perf tests: Exercise data type resolution from a register Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 07/11] perf tests: Silence the libbfd probe in build-test Arnaldo Carvalho de Melo
` (4 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The instruction stats are printed before the loop that accounts them,
so 'perf annotate --code-with-type --insn-stat' always shows zeros.
The data type counters are updated in --code-with-type mode too, but
only --data-type mode accounts their total, so they are incomplete
there.
Print both after the annotation loops, the data type part only with
--data-type.
Fixes: 58824fa0087e1cb7 ("perf annotate: Add --insn-stat option for debugging")
Fixes: 61a9741e9f78c64c ("perf annotate: Add --type-stat option for debugging")
Cc: Namhyung Kim <namhyung@kernel.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/builtin-annotate.c | 10 ++--
tools/perf/tests/shell/annotate_stats.sh | 64 ++++++++++++++++++++++++
2 files changed, 69 insertions(+), 5 deletions(-)
create mode 100755 tools/perf/tests/shell/annotate_stats.sh
diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
index 331adbdbd635917d..973f4950abd6f917 100644
--- a/tools/perf/builtin-annotate.c
+++ b/tools/perf/builtin-annotate.c
@@ -414,11 +414,6 @@ static void hists__find_annotations(struct hists *hists,
struct rb_node *nd = rb_first_cached(&hists->entries), *next;
int key = K_RIGHT;
- if (ann->type_stat)
- print_annotate_data_stat(&ann_data_stat);
- if (ann->insn_stat)
- print_annotate_item_stat(&ann_insn_stat, "Instruction");
-
while (nd) {
struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
struct annotation *notes;
@@ -660,6 +655,11 @@ static int __cmd_annotate(struct perf_annotate *ann)
hists__find_annotations(hists, pos, ann);
}
+ if (ann->data_type && ann->type_stat)
+ print_annotate_data_stat(&ann_data_stat);
+ if (ann->insn_stat)
+ print_annotate_item_stat(&ann_insn_stat, "Instruction");
+
if (use_browser == 2) {
void (*show_annotations)(void);
diff --git a/tools/perf/tests/shell/annotate_stats.sh b/tools/perf/tests/shell/annotate_stats.sh
new file mode 100755
index 0000000000000000..fe4891e8535d3944
--- /dev/null
+++ b/tools/perf/tests/shell/annotate_stats.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+# perf annotate instruction stats test
+# SPDX-License-Identifier: GPL-2.0
+
+set -e
+
+perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
+perfout=$(mktemp /tmp/__perf_test.perf.out.XXXXX)
+
+cleanup()
+{
+ rm -f "$perfdata" "$perfout"
+ trap - EXIT TERM INT
+}
+
+trap_cleanup()
+{
+ echo "Unexpected signal in ${FUNCNAME[1]}"
+ cleanup
+ exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+if ! perf check feature -q dwarf; then
+ echo "Skip: no DWARF (libdw) support"
+ cleanup
+ exit 2
+fi
+
+# No point in recording if the PMU cannot do memory events.
+if perf mem record -o /dev/null -- true 2>&1 | \
+ grep -q "failed: no PMU supports the memory events"; then
+ cleanup
+ exit 2
+fi
+if ! perf mem record -o /dev/null -- true 2>/dev/null; then
+ echo "Skip: cannot record memory events on this PMU"
+ cleanup
+ exit 2
+fi
+
+if ! perf mem record -o "$perfdata" -- perf test -w datasym > /dev/null 2>&1; then
+ echo "FAIL: perf mem record"
+ cleanup
+ exit 1
+fi
+
+if ! perf annotate --code-with-type --insn-stat -i "$perfdata" --stdio \
+ --percent-limit 1 > "$perfout" 2>/dev/null; then
+ echo "FAIL: perf annotate"
+ cleanup
+ exit 1
+fi
+
+# The instruction stats are accounted while the instructions are
+# displayed; printing them before the display loop shows zeros.
+if ! grep -Eq '^total [1-9][0-9]*,' "$perfout"; then
+ echo "FAIL: instruction stats not accounted"
+ cleanup
+ exit 1
+fi
+
+echo "instruction stats accounted"
+cleanup
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 07/11] perf tests: Silence the libbfd probe in build-test
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
` (5 preceding siblings ...)
2026-11-13 20:08 ` [PATCH v2 06/11] perf annotate: Print the stats after the annotations Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 08/11] perf build: Record the display-only features in FEATURE-DUMP Arnaldo Carvalho de Melo
` (3 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
build-test evaluates the libbfd probe at parse time and the compiler's
stderr isn't captured by $(shell ...), so without binutils-devel it
prints "bfd.h: No such file or directory" once per tests/make
invocation, five times per run.
Redirect the compiler's stderr; the empty result still skips the
nondistro tests as intended.
Fixes: 523471c5163659c6 ("perf build: Skip nondistro build test if libbfd is old")
Cc: James Clark <james.clark@linaro.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/tests/make | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/perf/tests/make b/tools/perf/tests/make
index 7524de5228029e7e..e6d7ac49b5b1693d 100644
--- a/tools/perf/tests/make
+++ b/tools/perf/tests/make
@@ -128,7 +128,7 @@ make_minimal += NO_SDT=1 NO_JVMTI=1 NO_LIBZSTD=1
make_minimal += NO_CAPSTONE=1
# binutils 2_42 and newer have bfd_thread_init()
-new_libbfd := $(shell echo '#include <bfd.h>' | $(CC) -E -x c - | grep bfd_thread_init)
+new_libbfd := $(shell echo '#include <bfd.h>' | $(CC) -E -x c - 2>/dev/null | grep bfd_thread_init)
# Whether libLLVM can be built, which needs the llvm-devel/llvm-dev headers and
# libraries of version 13 or newer, not just llvm-config. This mirrors the
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 08/11] perf build: Record the display-only features in FEATURE-DUMP
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
` (6 preceding siblings ...)
2026-11-13 20:08 ` [PATCH v2 07/11] perf tests: Silence the libbfd probe in build-test Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 09/11] perf tests: Fix the Intel PT virtual LBR check Arnaldo Carvalho de Melo
` (2 subsequent siblings)
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
llvm-perf and rust are in FEATURE_DISPLAY but not in FEATURE_TESTS: they
can't be part of the test-all fast path, which sets every FEATURE_TESTS
entry to 1 when test-all.c builds. Since FEATURE-DUMP is written from
FEATURE_TESTS only, a FEATURES_DUMP= build, as used by build-test, never
sees them and silently builds without libLLVM and the rust workloads.
Append the display features that aren't in FEATURE_TESTS to the dump
after the regular feature checks have run.
Fixes: 206dcfca1f5cfac3 ("perf build: Autodetect minimum required llvm-dev version")
Fixes: 3f5dfa472ea6771c ("tools build: Fix rust feature detection")
Cc: James Clark <james.clark@linaro.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/Makefile.config | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config
index b4764f69f47f4d8c..1b5caa246345c9bb 100644
--- a/tools/perf/Makefile.config
+++ b/tools/perf/Makefile.config
@@ -1310,3 +1310,7 @@ endif
# tests.
$(shell rm -f $(FEATURE_DUMP_FILENAME))
$(foreach feat,$(FEATURE_TESTS),$(shell echo "$(call feature_assign,$(feat))" >> $(FEATURE_DUMP_FILENAME)))
+# Features only in FEATURE_DISPLAY aren't part of the test-all fast path, so
+# they have to be checked explicitly and recorded here too, otherwise a
+# FEATURES_DUMP= build silently disables them (llvm-perf, rust).
+$(foreach feat,$(filter-out $(FEATURE_TESTS),$(FEATURE_DISPLAY)),$(shell echo "$(call feature_assign,$(feat))" >> $(FEATURE_DUMP_FILENAME)))
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 09/11] perf tests: Fix the Intel PT virtual LBR check
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
` (7 preceding siblings ...)
2026-11-13 20:08 ` [PATCH v2 08/11] perf build: Record the display-only features in FEATURE-DUMP Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 10/11] perf test: Drop samples without an IP in the dlfilter test Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 11/11] perf test: Skip the LBR inline unwinding check when unsupported Arnaldo Carvalho de Melo
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo, Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The record this check uses writes aux samples, and with the single data
page the other tests ask for they are lost:
Warning: Processed 29 samples and lost 100.00%
so no branches are decoded and the check fails with "Failed with virtual
lbr". Record with 4 data pages and 128 auxtrace pages: the same trace
decodes 64 branches and still fits the mlock budget.
Require the python perf module to provide session, not just to be
importable: a perf whose python bindings are not found can pick up an
unrelated module and fail with
Error processing events: module 'perf' has no attribute 'session'
instead of skipping the subtest.
Detected building perf on fedora:44 on the runner with Intel PT hardware,
where the subtest failed with "Failed with virtual lbr" in the container,
and with the python error when run as root on the same machine.
Fixes: 915d6b14dc0c ("perf test: Migrate Intel PT virtual LBR test to Python API")
Signed-off-by: Arnaldo Carvalho de Melo <acme@kernel.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/tests/shell/test_intel_pt.sh | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/tools/perf/tests/shell/test_intel_pt.sh b/tools/perf/tests/shell/test_intel_pt.sh
index c0ecece0a8255124..511440b5956a6897 100755
--- a/tools/perf/tests/shell/test_intel_pt.sh
+++ b/tools/perf/tests/shell/test_intel_pt.sh
@@ -384,13 +384,14 @@ test_virtual_lbr()
# shellcheck source=lib/setup_python.sh
. "${shelldir}"/lib/setup_python.sh
- if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then
- echo "SKIP: Python perf module not found"
+ if ! "$PYTHON" -c 'import perf; perf.session' > /dev/null 2>&1; then
+ echo "SKIP: Python perf module with session support not found"
return 2
fi
- # Check if virtual lbr is working
- perf_record_no_bpf -o "${tmpfile}" --aux-sample \
+ # Check if virtual lbr is working. This records aux samples, which a
+ # single data page overruns, so give it more room than the other tests.
+ perf_record_no_bpf -m 4,128 -o "${tmpfile}" --aux-sample \
-e '{intel_pt//,cycles}:u' perf test -w brstack || return 1
perf inject --itrace=L -i "${tmpfile}" -o "${perfdatafile}" || return 1
output=$("$PYTHON" "${shelldir}/lib/perf_brstack_max.py" -i "${perfdatafile}") || return 1
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 10/11] perf test: Drop samples without an IP in the dlfilter test
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
` (8 preceding siblings ...)
2026-11-13 20:08 ` [PATCH v2 09/11] perf tests: Fix the Intel PT virtual LBR check Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 11/11] perf test: Skip the LBR inline unwinding check when unsupported Arnaldo Carvalho de Melo
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo, Arnaldo Carvalho de Melo
Since v7.3 the kernel zeroes PERF_SAMPLE_IP when a userspace-only event
skids into kernel mode, so that kernel register state is not exposed to
an unprivileged event (166f10836a653dfa "perf/core: Fix kernel register
info leak via hardware skid").
The test dlfilter keeps those samples, and the check that nothing but
test_loop survives then trips on their lines: the comm column reads
"perf". Drop them, which is what the test wants, everything except
test_loop must be filtered out.
Fixes: 199d5e872a9ce737 ("perf tests script dlfilter: Add a dlfilter test")
Cc: Ian Rogers <irogers@google.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@kernel.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/tests/shell/script_dlfilter.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/perf/tests/shell/script_dlfilter.sh b/tools/perf/tests/shell/script_dlfilter.sh
index 88222280f6094fd0..797cd9ae84141f64 100755
--- a/tools/perf/tests/shell/script_dlfilter.sh
+++ b/tools/perf/tests/shell/script_dlfilter.sh
@@ -46,7 +46,7 @@ int filter_event(void *data, const struct perf_dlfilter_sample *sample, void *ct
const struct perf_dlfilter_al *al;
if (!sample->ip)
- return 0;
+ return 1;
al = perf_dlfilter_fns.resolve_ip(ctx);
if (!al || !al->sym || strcmp(al->sym, "test_loop"))
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 11/11] perf test: Skip the LBR inline unwinding check when unsupported
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
` (9 preceding siblings ...)
2026-11-13 20:08 ` [PATCH v2 10/11] perf test: Drop samples without an IP in the dlfilter test Arnaldo Carvalho de Melo
@ 2026-11-13 20:08 ` Arnaldo Carvalho de Melo
10 siblings, 0 replies; 12+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-11-13 20:08 UTC (permalink / raw)
To: Namhyung Kim
Cc: Ingo Molnar, Thomas Gleixner, James Clark, Jiri Olsa, Ian Rogers,
Adrian Hunter, Clark Williams, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
From: Arnaldo Carvalho de Melo <acme@redhat.com>
The check only looks for the PMU's caps/branches file before recording
with --call-graph lbr. AMD BRS advertises branches but only samples its
own retired branches event, so the record with cycles is rejected and
the test fails instead of skipping.
Probe the record and skip when it fails, as the branch stack test does.
Fixes: 446c595dc0dd ("perf test addr2line_inlines: Ensure inline information shows on LBR leaves")
Cc: Ian Rogers <irogers@google.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
---
tools/perf/tests/shell/addr2line_inlines.sh | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/tools/perf/tests/shell/addr2line_inlines.sh b/tools/perf/tests/shell/addr2line_inlines.sh
index e8754ef2d7f21ac2..6fe47909dae4f812 100755
--- a/tools/perf/tests/shell/addr2line_inlines.sh
+++ b/tools/perf/tests/shell/addr2line_inlines.sh
@@ -71,7 +71,14 @@ test_lbr() {
fi
# Record data. Currently only dwarf callchains support inlined functions.
- perf record --call-graph lbr -e cycles:u -o "${perf_data}" -- perf test -w inlineloop 1
+ # Some PMUs advertise branches but reject branch sampling for cycles, e.g.
+ # AMD BRS, which only samples its own retired branches event.
+ if ! perf record --call-graph lbr -e cycles:u -o "${perf_data}" -- \
+ perf test -w inlineloop 1 2>/dev/null
+ then
+ echo "Skip: LBR not supported by the PMU"
+ return
+ fi
# Check output with inline (default) and srcline
perf script -i "${perf_data}" --fields +srcline > "${perf_script_txt}"
--
2.56.0
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-11-13 20:09 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-13 20:08 [PATCH v2 0/11] perf coverage: A harness for perf, and the fixes it found Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 01/11] perf scripts: Add a coverage harness for perf Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 02/11] perf annotate-data: Resolve DW_OP_addrx global variables Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 03/11] perf tests: Exercise the flexible array member path Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 04/11] perf annotate-data: Count flexible array accesses for the whole struct Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 05/11] perf tests: Exercise data type resolution from a register Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 06/11] perf annotate: Print the stats after the annotations Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 07/11] perf tests: Silence the libbfd probe in build-test Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 08/11] perf build: Record the display-only features in FEATURE-DUMP Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 09/11] perf tests: Fix the Intel PT virtual LBR check Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 10/11] perf test: Drop samples without an IP in the dlfilter test Arnaldo Carvalho de Melo
2026-11-13 20:08 ` [PATCH v2 11/11] perf test: Skip the LBR inline unwinding check when unsupported Arnaldo Carvalho de Melo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®