mirror of https://lore.kernel.org/lkml/
 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: 13+ 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 " 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

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 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®