mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Leo Yan <leo.yan@arm.com>
To: 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>,
	Suyash Mahar <smahar@meta.com>,  Amir Ayupov <aaupov@fb.com>
Cc: linux-perf-users@vger.kernel.org, linux-kernel@vger.kernel.org,
	 Leo Yan <leo.yan@arm.com>
Subject: [PATCH 2/4] perf: Assemble split thread maps in script and stat report
Date: Sun, 15 Nov 2026 13:34:20 +0000	[thread overview]
Message-ID: <20261115-perf_fix_large_threads-v1-2-69c9b86b9ae5@arm.com> (raw)
In-Reply-To: <20261115-perf_fix_large_threads-v1-0-69c9b86b9ae5@arm.com>

perf script and perf stat report accept only the first thread-map record
and ignore subsequent records. For split maps, this leaves per-thread
counters sized for only part of the map.

Add thread_map__append_event() to assemble records in order, and reuse
it for single-record construction. Free any command names copied by a
failed append.

Keep the map private until the final record arrives. Publish it to the
evlist and allocate counters once both the complete thread map and CPU
map are available. This avoids reallocating a map already shared with
the evlist.

Signed-off-by: Leo Yan <leo.yan@arm.com>
---
 tools/perf/builtin-script.c  | 21 ++++++++++++++----
 tools/perf/builtin-stat.c    | 25 ++++++++++++++++------
 tools/perf/util/thread_map.c | 51 ++++++++++++++++++++++++++++++++++----------
 tools/perf/util/thread_map.h |  2 ++
 4 files changed, 77 insertions(+), 22 deletions(-)

diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index b6691ebb1b4eff14cabe99e4388368503af28a6b..e41925fb86b8d8acff5c85fb4951720fcb5dde0d 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -162,6 +162,7 @@ struct perf_script {
 	bool			show_cgroup_events;
 	bool			show_text_poke_events;
 	bool			allocated;
+	bool			threads_is_appending;
 	bool			per_event_dump;
 	bool			stitch_lbr;
 	struct evswitch		evswitch;
@@ -3416,6 +3417,10 @@ static int __cmd_script(struct perf_script *script)
 	}
 
 	ret = perf_session__process_events(script->session);
+	if (!ret && !session_done() && script->threads_is_appending) {
+		pr_err("Incomplete thread map: missing final record.\n");
+		ret = -EINVAL;
+	}
 
 	if (script->per_event_dump)
 		perf_script__exit_per_event_dump_stats(script);
@@ -3975,6 +3980,10 @@ static int set_maps(struct perf_script *script)
 {
 	struct evlist *evlist = script->session->evlist;
 
+	/* Appending can move the map, so share it only after the final record. */
+	if (script->threads_is_appending)
+		return 0;
+
 	if (!script->cpus || !script->threads)
 		return 0;
 
@@ -3996,18 +4005,22 @@ int process_thread_map_event(const struct perf_tool *tool,
 			     union perf_event *event)
 {
 	struct perf_script *script = container_of(tool, struct perf_script, tool);
+	int ret;
 
 	if (dump_trace)
 		perf_event__fprintf_thread_map(event, stdout);
 
-	if (script->threads) {
+	if (script->threads && !script->threads_is_appending) {
 		pr_warning("Extra thread map event, ignoring.\n");
 		return 0;
 	}
 
-	script->threads = thread_map__new_event(&event->thread_map);
-	if (!script->threads)
-		return -ENOMEM;
+	ret = thread_map__append_event(&script->threads, &event->thread_map);
+	if (ret)
+		return ret;
+
+	script->threads_is_appending =
+		event->header.misc & PERF_RECORD_MISC_THREAD_MAP_MORE;
 
 	return set_maps(script);
 }
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index 37752d6bfef451e1e8e5ccdddfa741c2c6795882..19d120b64706563251cec000eb7706cd4ad5897d 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -147,6 +147,7 @@ struct perf_stat {
 	u64			 bytes_written;
 	struct perf_tool	 tool;
 	bool			 maps_allocated;
+	bool			 threads_is_appending;
 	struct perf_cpu_map	*cpus;
 	struct perf_thread_map *threads;
 	enum aggr_mode		 aggr_mode;
@@ -2280,6 +2281,10 @@ int process_stat_config_event(const struct perf_tool *tool,
 
 static int set_maps(struct perf_stat *st)
 {
+	/* Appending can move the map, so share it only after the final record. */
+	if (st->threads_is_appending)
+		return 0;
+
 	if (!st->cpus || !st->threads)
 		return 0;
 
@@ -2301,15 +2306,19 @@ int process_thread_map_event(const struct perf_tool *tool,
 			     union perf_event *event)
 {
 	struct perf_stat *st = container_of(tool, struct perf_stat, tool);
+	int ret;
 
-	if (st->threads) {
+	if (st->threads && !st->threads_is_appending) {
 		pr_warning("Extra thread map event, ignoring.\n");
 		return 0;
 	}
 
-	st->threads = thread_map__new_event(&event->thread_map);
-	if (!st->threads)
-		return -ENOMEM;
+	ret = thread_map__append_event(&st->threads, &event->thread_map);
+	if (ret)
+		return ret;
+
+	st->threads_is_appending =
+		event->header.misc & PERF_RECORD_MISC_THREAD_MAP_MORE;
 
 	return set_maps(st);
 }
@@ -2405,11 +2414,13 @@ static int __cmd_report(int argc, const char **argv)
 	evsel_list         = session->evlist;
 
 	ret = perf_session__process_events(session);
-	if (ret)
-		return ret;
+	if (!ret && !session_done() && perf_stat.threads_is_appending) {
+		pr_err("Incomplete thread map: missing final record.\n");
+		ret = -EINVAL;
+	}
 
 	perf_session__delete(session);
-	return 0;
+	return ret;
 }
 
 static void setup_system_wide(int forks)
diff --git a/tools/perf/util/thread_map.c b/tools/perf/util/thread_map.c
index 918ab318023205e48ccc18d9a0dd675c1b77f1ed..4b3e0cd022fb79635b914610f32cb0c9c161568f 100644
--- a/tools/perf/util/thread_map.c
+++ b/tools/perf/util/thread_map.c
@@ -336,28 +336,57 @@ void thread_map__read_comms(struct perf_thread_map *threads)
 		comm_init(threads, i);
 }
 
-static void thread_map__copy_event(struct perf_thread_map *threads,
-				   struct perf_record_thread_map *event)
+int thread_map__append_event(struct perf_thread_map **map,
+			    const struct perf_record_thread_map *event)
 {
-	unsigned i;
+	struct perf_thread_map *threads = *map;
+	bool new_map = !threads;
+	u64 old_nr = threads ? threads->nr : 0, nr, i;
 
-	threads->nr = (int) event->nr;
+	/*
+	 * Trust event->nr to describe chunks of a valid thread map, whose total
+	 * count and allocation size are representable without overflow.
+	 */
+	nr = old_nr + event->nr;
+
+	threads = perf_thread_map__realloc(threads, nr);
+	if (!threads)
+		return -ENOMEM;
+
+	if (new_map) {
+		threads->nr = 0;
+		refcount_set(&threads->refcnt, 1);
+	}
+	*map = threads;
 
 	for (i = 0; i < event->nr; i++) {
-		perf_thread_map__set_pid(threads, i, (pid_t) event->entries[i].pid);
-		threads->map[i].comm = strndup(event->entries[i].comm, 16);
+		u64 idx = old_nr + i;
+
+		perf_thread_map__set_pid(threads, idx,
+					 (pid_t) event->entries[i].pid);
+		threads->map[idx].comm = strndup(event->entries[i].comm,
+						 sizeof(event->entries[i].comm));
+		if (!threads->map[idx].comm)
+			goto out_free;
 	}
+	threads->nr = nr;
 
-	refcount_set(&threads->refcnt, 1);
+	return 0;
+
+out_free:
+	while (i--)
+		zfree(&threads->map[old_nr + i].comm);
+	return -ENOMEM;
 }
 
 struct perf_thread_map *thread_map__new_event(struct perf_record_thread_map *event)
 {
-	struct perf_thread_map *threads;
+	struct perf_thread_map *threads = NULL;
 
-	threads = thread_map__alloc(event->nr);
-	if (threads)
-		thread_map__copy_event(threads, event);
+	if (thread_map__append_event(&threads, event)) {
+		perf_thread_map__put(threads);
+		return NULL;
+	}
 
 	return threads;
 }
diff --git a/tools/perf/util/thread_map.h b/tools/perf/util/thread_map.h
index d700a22b7ae4663aca843baf80f7e5f522d6e607..7ddb9dd3fe9f2525cc7ba358bd6294a293cac910 100644
--- a/tools/perf/util/thread_map.h
+++ b/tools/perf/util/thread_map.h
@@ -13,6 +13,8 @@ struct perf_thread_map *thread_map__new_by_pid(pid_t pid);
 struct perf_thread_map *thread_map__new_by_tid(pid_t tid);
 struct perf_thread_map *thread_map__new(pid_t pid, pid_t tid);
 struct perf_thread_map *thread_map__new_event(struct perf_record_thread_map *event);
+int thread_map__append_event(struct perf_thread_map **map,
+			     const struct perf_record_thread_map *event);
 
 struct perf_thread_map *thread_map__new_str(const char *pid,
 		const char *tid, bool all_threads);

-- 
2.34.1


  parent reply	other threads:[~2026-11-15 13:34 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-15 13:34 [PATCH 0/4] perf: Support thread maps larger than one record Leo Yan
2026-11-15 13:34 ` [PATCH 1/4] perf synthetic-events: Split oversized thread maps into records Leo Yan
2026-11-15 13:34 ` Leo Yan [this message]
2026-11-15 13:34 ` [PATCH 3/4] perf test: Cover multi-record thread map synthesis Leo Yan
2026-11-15 13:34 ` [PATCH 4/4] perf docs: Describe multi-record thread maps Leo Yan

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=20261115-perf_fix_large_threads-v1-2-69c9b86b9ae5@arm.com \
    --to=leo.yan@arm.com \
    --cc=aaupov@fb.com \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.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=namhyung@kernel.org \
    --cc=smahar@meta.com \
    /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®