mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Alireza Haghdoost via B4 Relay <devnull+haghdoost.uber.com@kernel.org>
To: Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	 Arnaldo Carvalho de Melo <acme@kernel.org>,
	 Namhyung Kim <namhyung@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	 Alexander Shishkin <alexander.shishkin@linux.intel.com>,
	 Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
	 Adrian Hunter <adrian.hunter@intel.com>,
	 James Clark <james.clark@linaro.org>,
	Alexei Starovoitov <ast@kernel.org>,
	 Andrii Nakryiko <andriin@fb.com>
Cc: linux-perf-users@vger.kernel.org, linux-kernel@vger.kernel.org,
	 Alireza Haghdoost <haghdoost@uber.com>
Subject: [PATCH v2 4/4] perf test: Test lazy symbol loading and symbol memory limits
Date: Sat, 19 Sep 2026 19:33:59 -0700	[thread overview]
Message-ID: <20260919-perf-symbol-memory-send-v2-4-495b8f00ad7c@uber.com> (raw)
In-Reply-To: <20260919-perf-symbol-memory-send-v2-0-495b8f00ad7c@uber.com>

From: Alireza Haghdoost <haghdoost@uber.com>

Add a perf script shell test for --lazy-load-symbols and
--max-symbol-bytes.

Record a small callchain fixture, require evidence that the controlled
perf DSO built an on-demand index, and compare only extracted occurrences
of the controlled test_loop symbol. This avoids coupling the test to
addresses, diagnostics, or architecture-specific symbols for which the
eager and lazy loaders have documented differences.

Derive the constrained lazy budget from the unlimited run's
index-reservation peak. This gives a deterministic boundary where the
index fits and later materialization reaches the limit. Also verify eager
limiting, malformed size rejection, and one-time warning behavior.

Report unsupported recording, missing controlled output, unavailable
libelf, and unavailable dependent data as skips without replacing a prior
failure. Keep helper returns safe under set -e. Add focused coverage for
skip-status preservation and truncated string-table reads in the lazy
materialization path.

Signed-off-by: Alireza Haghdoost <haghdoost@uber.com>
---
 tools/perf/tests/shell/script_lazy_load_symbols.sh | 278 +++++++++++++++++++++
 .../tests/shell/script_lazy_load_symbols_skip.sh   |  26 ++
 tools/perf/tests/symbol-bytes.c                    |  58 +++++
 3 files changed, 362 insertions(+)

diff --git a/tools/perf/tests/shell/script_lazy_load_symbols.sh b/tools/perf/tests/shell/script_lazy_load_symbols.sh
new file mode 100755
index 000000000000..7e193c1c3d1f
--- /dev/null
+++ b/tools/perf/tests/shell/script_lazy_load_symbols.sh
@@ -0,0 +1,278 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf script lazy symbol loading tests (exclusive)
+#
+# Verifies that --lazy-load-symbols matches the default eager loader for a
+# controlled symbol, and that --max-symbol-bytes caps symbol allocations
+# (emitting [unknown] plus a warning) without crashing.
+
+mark_skip() {
+	if [ "${err}" -eq 0 ]; then
+		err=2
+	fi
+	return 0
+}
+
+if [ "${PERF_LAZY_LOAD_SYMBOLS_TEST_HELPERS:-}" = 1 ]; then
+	return 0
+fi
+
+set -e
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/perf_has_symbol.sh
+. "${shelldir}"/lib/perf_has_symbol.sh
+
+testsym="test_loop"
+perf_path=$(readlink -f "$(command -v perf)")
+lazy_index_budget=
+
+skip_test_missing_symbol ${testsym}
+
+if ! perf check feature -q libelf
+then
+	echo "Lazy symbol loading [Skipped no libelf support]"
+	exit 2
+fi
+
+err=0
+temp_dir=$(mktemp -d /tmp/__perf_test.lazy_load.XXXXX)
+perfdata="${temp_dir}/perf.data"
+eager_out="${temp_dir}/eager.out"
+lazy_out="${temp_dir}/lazy.out"
+lazy_err="${temp_dir}/lazy.err"
+eager_sym_out="${temp_dir}/eager.sym.out"
+lazy_sym_out="${temp_dir}/lazy.sym.out"
+
+cleanup() {
+	rm -rf "${temp_dir}"
+	trap - EXIT TERM INT
+}
+
+trap_cleanup() {
+	echo "Unexpected signal in ${FUNCNAME[1]}"
+	cleanup
+	exit 1
+}
+trap trap_cleanup EXIT TERM INT
+
+test_lazy_load_identical() {
+	echo "Lazy-load output matches eager loader"
+
+	# Record a small profile with callchains so symbol resolution runs.
+	if ! perf record -o "${perfdata}" -g -- perf test -w thloop 2> /dev/null
+	then
+		echo "Lazy-load identical [Skipped record not supported]"
+		mark_skip
+		return 0
+	fi
+
+	if ! perf script -i "${perfdata}" 2> /dev/null > "${eager_out}" || \
+	   ! perf script -v --lazy-load-symbols -i "${perfdata}" \
+		2> "${lazy_err}" > "${lazy_out}"
+	then
+		echo "Lazy-load identical [Failed perf script error]"
+		err=1
+		return
+	fi
+	if ! grep -q "on-demand index:" "${lazy_err}"
+	then
+		echo "Lazy-load identical [Failed lazy loader fell back to eager]"
+		err=1
+		return
+	fi
+	lazy_index_budget=$(awk -v dso="${perf_path}: on-demand index:" \
+		'index($0, dso) { sub(/^.* budget=/, ""); print; exit }' \
+		"${lazy_err}")
+	case "${lazy_index_budget}" in
+	''|*[!0-9]*)
+		echo "Lazy-load identical [Failed controlled DSO has no index]"
+		err=1
+		return
+		;;
+	esac
+
+	# The comparison is only meaningful if something actually resolved;
+	# two all-[unknown] outputs would also match.
+	if ! grep -q "${testsym}" "${eager_out}"
+	then
+		echo "Lazy-load identical [Skipped no ${testsym} resolved]"
+		mark_skip
+		return 0
+	fi
+
+	grep -w -o "${testsym}" "${eager_out}" > "${eager_sym_out}"
+	if ! grep -w -o "${testsym}" "${lazy_out}" > "${lazy_sym_out}"
+	then
+		echo "Lazy-load identical [Failed no lazy ${testsym} resolved]"
+		err=1
+		return
+	fi
+
+	if ! cmp -s "${eager_sym_out}" "${lazy_sym_out}"
+	then
+		echo "Lazy-load identical [Failed ${testsym} output differs]"
+		err=1
+		return
+	fi
+	echo "Lazy-load identical [Success]"
+}
+
+test_max_symbol_bytes() {
+	echo "--max-symbol-bytes budget enforcement"
+
+	# Depends on ${perfdata} from test_lazy_load_identical.
+	if [ ! -s "${perfdata}" ]
+	then
+		echo "--max-symbol-bytes budget [Skipped record not supported]"
+		mark_skip
+		return 0
+	fi
+
+	# A tiny budget forces most symbols to be dropped as [unknown],
+	# with a single warning, and must not crash.
+	if ! perf script --max-symbol-bytes=1K -i "${perfdata}" > /dev/null \
+		2> "${temp_dir}/budget.err"
+	then
+		echo "--max-symbol-bytes budget [Failed nonzero exit]"
+		err=1
+		return
+	fi
+	if ! grep -q "symbol memory budget exceeded" "${temp_dir}/budget.err"
+	then
+		echo "--max-symbol-bytes budget [Failed missing warning]"
+		err=1
+		return
+	fi
+	if perf script --max-symbol-bytes=1Kjunk -i "${perfdata}" \
+		> /dev/null 2>&1
+	then
+		echo "--max-symbol-bytes budget [Failed malformed size accepted]"
+		err=1
+		return
+	fi
+	if ! perf script --max-symbol-bytes=0 -i "${perfdata}" \
+		> /dev/null 2>&1
+	then
+		echo "--max-symbol-bytes budget [Failed zero not accepted]"
+		err=1
+		return
+	fi
+
+	# The unlimited run logged the peak accounted bytes at the controlled
+	# DSO's index reservation, before alias dedup may have released bytes.
+	# Reuse that peak as the budget: deterministic index construction fits,
+	# while subsequent materialization must hit the limit.
+	if ! perf script -v --lazy-load-symbols \
+		--max-symbol-bytes="${lazy_index_budget}B" \
+		-i "${perfdata}" > /dev/null 2> "${temp_dir}/lazy-budget.err"
+	then
+		echo "--max-symbol-bytes lazy budget [Failed nonzero exit]"
+		err=1
+		return
+	fi
+	if ! grep -Fq "${perf_path}: on-demand index:" \
+		"${temp_dir}/lazy-budget.err" ||
+	   ! grep -q "symbol memory budget exceeded" "${temp_dir}/lazy-budget.err"
+	then
+		echo "--max-symbol-bytes lazy budget [Failed no indexed budget case]"
+		err=1
+		return
+	fi
+	warnings=$(grep -c "symbol memory budget exceeded" \
+		"${temp_dir}/lazy-budget.err" || true)
+	if [ "${warnings}" -ne 1 ]
+	then
+		echo "--max-symbol-bytes lazy budget [Failed warning count: ${warnings}]"
+		err=1
+		return
+	fi
+	echo "--max-symbol-bytes budget [Success]"
+}
+
+test_budget_truncation_range() {
+	local longsym
+	local first_symbol
+	local trunc_source="${temp_dir}/truncation.S"
+	local trunc_binary="${temp_dir}/truncation"
+	local trunc_data="${temp_dir}/truncation.data"
+	local trunc_out="${temp_dir}/truncation.out"
+	local trunc_err="${temp_dir}/truncation.err"
+
+	echo "--max-symbol-bytes truncation range"
+
+	if [ "$(uname -m)" != x86_64 ]; then
+		echo "--max-symbol-bytes truncation range [Skipped x86_64 only]"
+		mark_skip
+		return 0
+	fi
+
+	longsym=$(printf 's%.0s' {1..900})
+	cat > "${trunc_source}" <<EOF
+	.text
+	.globl ${longsym}
+	.type ${longsym}, @function
+${longsym}:
+	call omitted_symbol
+	mov \$60, %eax
+	xor %edi, %edi
+	syscall
+
+	.globl omitted_symbol
+	.type omitted_symbol, @function
+omitted_symbol:
+	mov \$500000000, %ecx
+1:
+	dec %ecx
+	jnz 1b
+	ret
+	.size omitted_symbol, .-omitted_symbol
+EOF
+	if ! cc -nostdlib -no-pie -Wl,--build-id=none -Wl,-e,"${longsym}" \
+		-o "${trunc_binary}" "${trunc_source}"
+	then
+		echo "--max-symbol-bytes truncation range [Skipped compiler unsupported]"
+		mark_skip
+		return 0
+	fi
+
+	first_symbol=$(readelf -W -s "${trunc_binary}" |
+		awk '$4 == "FUNC" && $7 != "UND" { print $8; exit }')
+	if [ "${first_symbol}" != "${longsym}" ]; then
+		echo "--max-symbol-bytes truncation range [Skipped unexpected symbol order]"
+		mark_skip
+		return 0
+	fi
+
+	if ! perf record -o "${trunc_data}" -e cycles:u -F 1000 -- \
+		"${trunc_binary}" 2> /dev/null
+	then
+		echo "--max-symbol-bytes truncation range [Skipped record not supported]"
+		mark_skip
+		return 0
+	fi
+	if ! perf script --max-symbol-bytes=1K -i "${trunc_data}" -F ip,sym,dso \
+		> "${trunc_out}" 2> "${trunc_err}"
+	then
+		echo "--max-symbol-bytes truncation range [Failed perf script error]"
+		err=1
+		return
+	fi
+
+	if ! grep -q "symbol memory budget exceeded" "${trunc_err}" ||
+	   ! grep -F "${trunc_binary}" "${trunc_out}" | grep -q '\[unknown\]' ||
+	   grep -Fq "${longsym}" "${trunc_out}"
+	then
+		echo "--max-symbol-bytes truncation range [Failed omitted range resolved]"
+		err=1
+		return
+	fi
+	echo "--max-symbol-bytes truncation range [Success]"
+}
+
+test_lazy_load_identical
+test_max_symbol_bytes
+test_budget_truncation_range
+
+cleanup
+exit $err
diff --git a/tools/perf/tests/shell/script_lazy_load_symbols_skip.sh b/tools/perf/tests/shell/script_lazy_load_symbols_skip.sh
new file mode 100755
index 000000000000..136503863fdd
--- /dev/null
+++ b/tools/perf/tests/shell/script_lazy_load_symbols_skip.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# perf script lazy symbol loading skip status
+
+set -e
+
+shelldir=$(dirname "$0")
+PERF_LAZY_LOAD_SYMBOLS_TEST_HELPERS=1
+. "${shelldir}"/script_lazy_load_symbols.sh
+unset PERF_LAZY_LOAD_SYMBOLS_TEST_HELPERS
+
+err=0
+mark_skip
+if [ "${err}" -ne 2 ]; then
+	echo "Lazy-load skip status [Failed expected 2, got ${err}]"
+	exit 1
+fi
+
+err=1
+mark_skip
+if [ "${err}" -ne 1 ]; then
+	echo "Lazy-load skip status [Failed skip overwrote failure: ${err}]"
+	exit 1
+fi
+
+echo "Lazy-load skip status [Success]"
diff --git a/tools/perf/tests/symbol-bytes.c b/tools/perf/tests/symbol-bytes.c
index e5e2e6b9db97..0c9424fe4d1c 100644
--- a/tools/perf/tests/symbol-bytes.c
+++ b/tools/perf/tests/symbol-bytes.c
@@ -172,6 +172,63 @@ static int test__symbol_bytes_duplicate_selection(struct test_suite *test __mayb
 }
 
 #ifdef HAVE_LIBELF_SUPPORT
+static int truncated_name_case(size_t file_size, unsigned int expected_reads)
+{
+	char path[] = "/tmp/perf-lazy-truncated-XXXXXX";
+	struct dso *data_dso = NULL;
+	char *contents = NULL;
+	char *name_heap = NULL;
+	char namebuf[1024];
+	const char *name;
+	unsigned int nr_reads;
+	int ret = TEST_FAIL;
+	int fd = -1;
+
+	contents = malloc(file_size);
+	if (!contents)
+		goto out;
+	memset(contents, 'a', file_size);
+
+	fd = mkstemp(path);
+	if (fd < 0 || write(fd, contents, file_size) != (ssize_t)file_size)
+		goto out;
+	close(fd);
+	fd = -1;
+
+	data_dso = dso__new(path);
+	if (!data_dso || dso__data_set_path(data_dso, path) < 0)
+		goto out;
+	dso__set_binary_type(data_dso, DSO_BINARY_TYPE__SYSTEM_PATH_DSO);
+	name = dso__read_ondemand_symbol_name(data_dso, 0, 8192, 0,
+					      namebuf, sizeof(namebuf),
+					      &name_heap, &nr_reads);
+	if (name || name_heap || nr_reads != expected_reads)
+		goto out;
+	ret = TEST_OK;
+out:
+	if (fd >= 0)
+		close(fd);
+	if (data_dso)
+		dso__put(data_dso);
+	unlink(path);
+	free(name_heap);
+	free(contents);
+	return ret;
+}
+
+static int test__symbol_bytes_truncated_name(struct test_suite *test __maybe_unused,
+					     int subtest __maybe_unused)
+{
+	/*
+	 * One byte is short in the stack-buffer read.  1023 bytes fills it
+	 * exactly, so the following read exercises the heap-buffer path.
+	 */
+	if (truncated_name_case(1, 1) != TEST_OK ||
+	    truncated_name_case(1023, 2) != TEST_OK)
+		return TEST_FAIL;
+	return TEST_OK;
+}
+
 static int test__symbol_bytes_lazy_name_lookup(struct test_suite *test __maybe_unused,
 					       int subtest __maybe_unused)
 {
@@ -271,6 +328,7 @@ static struct test_case tests__symbol_bytes[] = {
 	TEST_CASE("Concurrent strict reservations", symbol_bytes_reservation),
 	TEST_CASE("Shared duplicate selection", symbol_bytes_duplicate_selection),
 #ifdef HAVE_LIBELF_SUPPORT
+	TEST_CASE("Truncated lazy symbol names", symbol_bytes_truncated_name),
 	TEST_CASE("Lazy address and name lookup", symbol_bytes_lazy_name_lookup),
 #endif
 	{ .name = NULL, }

-- 
Git-157)



      parent reply	other threads:[~2026-09-20  2:34 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-20  2:33 [PATCH v2 0/4] perf script: Bounded and lazy symbol loading Alireza Haghdoost via B4 Relay
2026-09-20  2:33 ` [PATCH v2 1/4] perf symbols: Fix broken ELF_C_READ_MMAP fallback guard Alireza Haghdoost via B4 Relay
2026-09-20 23:52   ` Namhyung Kim
2026-09-20  2:33 ` [PATCH v2 2/4] perf script: Add --max-symbol-bytes to bound ELF symbol memory Alireza Haghdoost via B4 Relay
2026-09-21  0:03   ` Namhyung Kim
2026-09-21  4:27     ` Alireza Haghdoost
2026-09-20  2:33 ` [PATCH v2 3/4] perf script: Add --lazy-load-symbols for lazy symbol loading Alireza Haghdoost via B4 Relay
2026-09-21  0:19   ` Namhyung Kim
2026-09-21  4:45     ` Alireza Haghdoost
2026-09-20  2:33 ` Alireza Haghdoost via B4 Relay [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260919-perf-symbol-memory-send-v2-4-495b8f00ad7c@uber.com \
    --to=devnull+haghdoost.uber.com@kernel.org \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=alexander.shishkin@linux.intel.com \
    --cc=andriin@fb.com \
    --cc=ast@kernel.org \
    --cc=haghdoost@uber.com \
    --cc=irogers@google.com \
    --cc=james.clark@linaro.org \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=mingo@redhat.com \
    --cc=namhyung@kernel.org \
    --cc=peterz@infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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®