All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ian Rogers <irogers@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	 Arnaldo Carvalho de Melo <acme@kernel.org>,
	Namhyung Kim <namhyung@kernel.org>, Jiri Olsa <jolsa@kernel.org>,
	 Ian Rogers <irogers@google.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	 James Clark <james.clark@linaro.org>,
	linux-perf-users@vger.kernel.org,  linux-kernel@vger.kernel.org,
	Alireza Haghdoost <haghdoost@uber.com>
Subject: [PATCH v1 7/7] perf test symbols: Add tests for symbol and DSO LRU shrinking
Date: Mon, 28 Sep 2026 00:52:37 -0700	[thread overview]
Message-ID: <20260928075237.3055101-8-irogers@google.com> (raw)
In-Reply-To: <20260928075237.3055101-1-irogers@google.com>

Add unit tests in tools/perf/tests/symbols.c covering:
- symbols__shrink(): Second-Chance / Clock eviction of unaccessed
  symbols, clearing of SYMBOL_FLAG_ACCESSED on survival, and retention
  of symbols with external reference counts (refcnt > 1).
- dsos__shrink(): Second-Chance / Clock eviction of cold DSOs from
  struct dsos while retaining accessed DSOs and externally referenced
  DSOs.
- dso__shrink() and map__find_symbol(): Shrinking a loaded DSO's symbol
  table and verifying that a subsequent map__find_symbol() transparently
  reloads the symbol table on demand.

Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/tests/symbols.c | 228 +++++++++++++++++++++++++++++++++++++
 1 file changed, 228 insertions(+)

diff --git a/tools/perf/tests/symbols.c b/tools/perf/tests/symbols.c
index 440d800d9f4d..6113acbaf602 100644
--- a/tools/perf/tests/symbols.c
+++ b/tools/perf/tests/symbols.c
@@ -3,6 +3,8 @@
 #include <linux/string.h>
 #include <sys/mman.h>
 #include <limits.h>
+#include <stdlib.h>
+#include <string.h>
 #include "debug.h"
 #include "dso.h"
 #include "env.h"
@@ -178,6 +180,221 @@ static int process_subdivided_dso(struct machine *machine, struct dso *dso)
 	return ret < 0 ? TEST_FAIL : TEST_OK;
 }
 
+static int test_symbols_shrink(void)
+{
+	struct symbols symbols;
+	struct symbol *sym1, *sym2, *sym3, *held;
+	int ret = TEST_FAIL;
+
+	symbols__init(&symbols);
+
+	sym1 = symbol__new(0x100, 0x50, STB_GLOBAL, STT_FUNC, "sym1");
+	sym2 = symbol__new(0x200, 0x50, STB_GLOBAL, STT_FUNC, "sym2");
+	sym3 = symbol__new(0x300, 0x50, STB_GLOBAL, STT_FUNC, "sym3");
+	if (!sym1 || !sym2 || !sym3) {
+		symbol__put(sym1);
+		symbol__put(sym2);
+		symbol__put(sym3);
+		goto out;
+	}
+
+	/* Hold an active reference on sym1, and mark sym2 accessed. */
+	held = symbol__get(sym1);
+	symbol__set_accessed(sym2, true);
+
+	symbols__insert(&symbols, sym1);
+	symbols__insert(&symbols, sym2);
+	symbols__insert(&symbols, sym3);
+
+	/*
+	 * First shrink: sym3 (unreferenced) is evicted; sym2 gets a second
+	 * chance because SYMBOL_FLAG_ACCESSED was set; sym1 has refcnt > 1.
+	 */
+	if (symbols__shrink(&symbols) != 1 || symbols.cnt != 2) {
+		pr_debug("First symbols__shrink failed (cnt=%u)!\n", symbols.cnt);
+		symbol__put(held);
+		goto out;
+	}
+
+	/*
+	 * Second shrink: sym2 was not accessed since first shrink, so it is now
+	 * evicted; sym1 still has refcnt > 1.
+	 */
+	if (symbols__shrink(&symbols) != 1 || symbols.cnt != 1) {
+		pr_debug("Second symbols__shrink failed (cnt=%u)!\n", symbols.cnt);
+		symbol__put(held);
+		goto out;
+	}
+
+	symbol__set_accessed(held, false);
+	symbol__put(held);
+	if (symbols__shrink(&symbols) != 1 || symbols.cnt != 0) {
+		pr_debug("Third symbols__shrink failed (cnt=%u)!\n", symbols.cnt);
+		goto out;
+	}
+
+	ret = TEST_OK;
+out:
+	symbols__exit(&symbols);
+	return ret;
+}
+
+static int test_dsos_shrink(void)
+{
+	struct dsos dsos;
+	struct dso *dso1, *dso2;
+	int ret = TEST_FAIL;
+
+	dsos__init(&dsos);
+
+	dso1 = dsos__findnew_id(&dsos, "/tmp/perf_test_dso1", NULL);
+	dso2 = dsos__findnew_id(&dsos, "/tmp/perf_test_dso2", NULL);
+	if (!dso1 || !dso2) {
+		dso__put(dso1);
+		dso__put(dso2);
+		goto out;
+	}
+
+	/* Keep the caller's active reference on dso1; drop caller reference on dso2. */
+	dso__put(dso2);
+
+	/*
+	 * First shrink: both DSOs had accessed=true from creation, so neither
+	 * is evicted on the first pass (Clock second chance), and accessed is
+	 * cleared.
+	 */
+	if (dsos__shrink(&dsos) != 0 || dsos.cnt != 2) {
+		pr_debug("First dsos__shrink failed (cnt=%u)!\n", dsos.cnt);
+		dso__put(dso1);
+		goto out;
+	}
+
+	/*
+	 * Second shrink: dso2 has refcnt == 1 and accessed == false, so it is
+	 * evicted; dso1 has refcnt == 2, so it is kept.
+	 */
+	if (dsos__shrink(&dsos) != 1 || dsos.cnt != 1) {
+		pr_debug("Second dsos__shrink failed (cnt=%u)!\n", dsos.cnt);
+		dso__put(dso1);
+		goto out;
+	}
+
+	dso__put(dso1);
+	if (dsos__shrink(&dsos) != 1 || dsos.cnt != 0) {
+		pr_debug("Third dsos__shrink failed (cnt=%u)!\n", dsos.cnt);
+		goto out;
+	}
+
+	ret = TEST_OK;
+out:
+	dsos__exit(&dsos);
+	return ret;
+}
+
+struct shrink_reload_find_args {
+	struct symbol *kept;
+	u64 first_start;
+	u64 second_start;
+	char *second_name;
+};
+
+static int shrink_reload_find_cb(struct symbol *sym, void *data)
+{
+	struct shrink_reload_find_args *args = data;
+
+	if (!args->kept) {
+		args->kept = symbol__get(sym);
+		args->first_start = symbol__start(sym);
+		return 0;
+	}
+	if (symbol__start(sym) != args->first_start) {
+		args->second_start = symbol__start(sym);
+		args->second_name = strdup(symbol__name(sym));
+		return args->second_name ? 1 : -ENOMEM;
+	}
+	return 0;
+}
+
+static int test_dso_shrink_and_reload(struct dso *dso, struct map *map)
+{
+	struct shrink_reload_find_args args = {
+		.kept = NULL,
+		.first_start = 0,
+		.second_start = 0,
+		.second_name = NULL,
+	};
+	struct symbol *sym, *reloaded;
+	size_t removed;
+
+	if (!dso__can_shrink_symbols(dso) || dso__kernel(dso))
+		return TEST_OK;
+
+	if (dso__for_each_symbol(dso, shrink_reload_find_cb, &args) < 0) {
+		symbol__put(args.kept);
+		return TEST_FAIL;
+	}
+	if (!args.second_name) {
+		symbol__put(args.kept);
+		return TEST_OK;
+	}
+
+	/* Unreferenced symbols have SYMBOL_FLAG_ACCESSED == false and are evicted immediately. */
+	removed = dso__shrink(dso);
+	if (removed == 0 || !dso__symbols_shrunk(dso)) {
+		pr_debug("dso__shrink did not evict any symbols!\n");
+		free(args.second_name);
+		symbol__put(args.kept);
+		return TEST_FAIL;
+	}
+
+	/* Kept symbol is still found without reloading. */
+	sym = dso__find_symbol(dso, args.first_start);
+	if (!RC_CHK_EQUAL(sym, args.kept)) {
+		pr_debug("Kept symbol not found after dso__shrink!\n");
+		symbol__put(sym);
+		free(args.second_name);
+		symbol__put(args.kept);
+		return TEST_FAIL;
+	}
+	symbol__put(sym);
+
+	/* Evicted symbol is no longer in dso->symbols before reload. */
+	sym = dso__find_symbol(dso, args.second_start);
+	if (sym != NULL) {
+		pr_debug("Evicted symbol still present after dso__shrink!\n");
+		symbol__put(sym);
+		free(args.second_name);
+		symbol__put(args.kept);
+		return TEST_FAIL;
+	}
+
+	/* Lookup via map__find_symbol transparently reloads the DSO. */
+	reloaded = map__find_symbol(map, args.second_start);
+	if (!reloaded || strcmp(symbol__name(reloaded), args.second_name) != 0) {
+		pr_debug("Failed to reload evicted symbol %s!\n", args.second_name);
+		symbol__put(reloaded);
+		free(args.second_name);
+		symbol__put(args.kept);
+		return TEST_FAIL;
+	}
+	symbol__put(reloaded);
+
+	/* Kept symbol pointer was preserved across reload deduplication. */
+	sym = dso__find_symbol(dso, args.first_start);
+	if (!RC_CHK_EQUAL(sym, args.kept)) {
+		pr_debug("Kept symbol pointer was not preserved across reload!\n");
+		symbol__put(sym);
+		free(args.second_name);
+		symbol__put(args.kept);
+		return TEST_FAIL;
+	}
+	symbol__put(sym);
+
+	free(args.second_name);
+	symbol__put(args.kept);
+	return TEST_OK;
+}
+
 static int test_file(struct test_info *ti, char *filename)
 {
 	struct map *map = NULL;
@@ -209,6 +426,9 @@ static int test_file(struct test_info *ti, char *filename)
 	/* Module dso is split into many dsos by section */
 	if (ret == TEST_OK && dso__kernel(dso) != DSO_SPACE__USER)
 		ret = process_subdivided_dso(ti->machine, dso);
+
+	if (ret == TEST_OK)
+		ret = test_dso_shrink_and_reload(dso, map);
 out_put:
 	map__put(map);
 
@@ -221,6 +441,14 @@ static int test__symbols(struct test_suite *test __maybe_unused, int subtest __m
 	struct test_info ti;
 	int ret;
 
+	ret = test_symbols_shrink();
+	if (ret != TEST_OK)
+		return ret;
+
+	ret = test_dsos_shrink();
+	if (ret != TEST_OK)
+		return ret;
+
 	ret = init_test_info(&ti);
 	if (ret != TEST_OK)
 		return ret;
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


  parent reply	other threads:[~2026-09-28  7:53 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-28  7:52 [PATCH v1 0/7] perf symbol: Reference counting, flat array storage, and LRU shrinking Ian Rogers
2026-09-28  7:52 ` [PATCH v1 1/7] perf symbol: Add accessor functions for struct symbol fields Ian Rogers
2026-09-28  7:52 ` [PATCH v1 2/7] perf symbol: Remove symbol_conf.priv_size and negative-offset allocations Ian Rogers
2026-09-28  7:52 ` [PATCH v1 3/7] perf symbol: Switch backing storage from rbtree to struct symbols array Ian Rogers
2026-09-28  7:52 ` [PATCH v1 4/7] perf symbol: Add reference counting and DECLARE_RC_STRUCT(symbol) Ian Rogers
2026-09-28  7:52 ` [PATCH v1 5/7] perf symbol: Add LRU memory shrinking for symbols, DSOs, and machines Ian Rogers
2026-09-28  7:52 ` [PATCH v1 6/7] perf session: Periodically shrink symbols and DSOs during event processing Ian Rogers
2026-09-28  7:52 ` Ian Rogers [this message]
2026-09-28 15:37 ` [PATCH v1 0/7] perf symbol: Reference counting, flat array storage, and LRU shrinking Ian Rogers
2026-09-28 19:45 ` Alireza Haghdoost
2026-09-28 20:55   ` Ian Rogers
2026-09-28 21:51     ` Namhyung Kim
2026-09-28 22:04       ` Ian Rogers
2026-09-29  0:12         ` Alireza Haghdoost
2026-09-29 18:09           ` Namhyung Kim
2026-09-29 21:24             ` Alireza Haghdoost
2026-09-29 18:06         ` Namhyung Kim

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=20260928075237.3055101-8-irogers@google.com \
    --to=irogers@google.com \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=haghdoost@uber.com \
    --cc=james.clark@linaro.org \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.