Linux Perf Users
 help / color / mirror / Atom feed
* [PATCH v1] perf top: Merge hybrid common events
@ 2026-08-13 13:25 Andi Kleen
  2026-08-13 13:57 ` sashiko-bot
  0 siblings, 1 reply; 2+ messages in thread
From: Andi Kleen @ 2026-08-13 13:25 UTC (permalink / raw)
  To: namhyung; +Cc: acme, linux-perf-users, Andi Kleen, Andi Kleen

From: Andi Kleen <ak@kernel.org>

One annoyance with perf top on a hybrid system is that it requires to
chose which hybrid PMU to sample on. Normally I want to sample the whole
system and don't know on which cores my workload ends up.

This patch automatically merges the two PMUs when the event is present
in both PMUs. For now it only handles simple TYPE_HARDWARE cases, like
cycles
(could be later extended for TYPE_RAW too by checking the json name is the same)
The behavior can be disabled with --no-hybrid-merge

Assisted-by: omp:GLM-5.2
Signed-off-by: Andi Kleen <ak@linux.intel.com>
---
 tools/perf/Documentation/perf-top.txt |  6 ++++
 tools/perf/builtin-top.c              | 16 ++++++++++
 tools/perf/ui/hist.c                  | 19 ++++++++++++
 tools/perf/util/evlist.c              | 43 +++++++++++++++++++++++++++
 tools/perf/util/evlist.h              |  1 +
 tools/perf/util/hist.h                | 15 +++++++++-
 tools/perf/util/top.c                 |  8 ++++-
 tools/perf/util/top.h                 |  1 +
 8 files changed, 107 insertions(+), 2 deletions(-)

diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt
index af3e4230c72f4..e560d6b1634d8 100644
--- a/tools/perf/Documentation/perf-top.txt
+++ b/tools/perf/Documentation/perf-top.txt
@@ -43,6 +43,12 @@ Default is to monitor all CPUS.
 	encoding with the layout of the event control registers as described
 	by entries in /sys/bus/event_source/devices/cpu/format/*.
 
+--hybrid-merge::
+    Merge matching legacy hardware events from all hybrid core PMUs into one
+    display. This is enabled by default when the same event is available on
+    each core PMU. Use `--no-hybrid-merge` to display the existing per-event
+    selection menu instead.
+
 --filter=<filter>::
 	Event filter.  This option should follow an event selector (-e). For
 	syntax see linkperf:perf-record[1].
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index 1211401616ee3..19dec094683b3 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -1334,6 +1334,19 @@ static int __cmd_top(struct perf_top *top)
 	 */
         if (!target__none(&opts->target))
 		evlist__enable(top->evlist);
+	if (top->hybrid_merge && !symbol_conf.report_hierarchy &&
+	    evlist__can_merge_hybrid(top->evlist)) {
+		struct evsel *leader = evlist__first(top->evlist);
+
+		/*
+		 * Merged events are not true groups, but can use
+		 * the existing group display code to display them
+		 * anyways.
+		 */
+		__perf_evlist__set_leader(&top->evlist->core.entries, &leader->core);
+		evsel__hists(leader)->merge_entries = true;
+		symbol_conf.event_group = true;
+	}
 
 	ret = -1;
 	if (pthread_create(&thread_process, NULL, process_thread, top)) {
@@ -1457,6 +1470,7 @@ int cmd_top(int argc, const char **argv)
 	struct perf_top top = {
 		.count_filter	     = 5,
 		.delay_secs	     = 2,
+		.hybrid_merge	     = true,
 		.record_opts = {
 			.mmap_pages	= UINT_MAX,
 			.user_freq	= UINT_MAX,
@@ -1490,6 +1504,8 @@ int cmd_top(int argc, const char **argv)
 	OPT_CALLBACK('e', "event", &parse_events_option_args, "event",
 		     "event selector. use 'perf list' to list available events",
 		     parse_events_option),
+	OPT_BOOLEAN(0, "hybrid-merge", &top.hybrid_merge,
+		    "merge the same event across hybrid core PMUs"),
 	OPT_CALLBACK(0, "filter", &top.evlist, "filter",
 		     "event filter", parse_filter),
 	OPT_U64('c', "count", &opts->user_interval, "event period to sample"),
diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c
index e58327595d37d..aee43b33e4e1e 100644
--- a/tools/perf/ui/hist.c
+++ b/tools/perf/ui/hist.c
@@ -287,6 +287,25 @@ static int __hpp__sort(struct hist_entry *a, struct hist_entry *b,
 		return __hpp__group_sort_idx(a, b, get_field,
 					     symbol_conf.group_sort_idx);
 	}
+	/*
+	 * Relies on merge_entries being only enabled if there are
+	 * only matching events. If that is ever relaxed will need
+	 * more logic here.
+	 */
+	if (a->hists->merge_entries && b->hists->merge_entries) {
+		u64 val_a = get_field(a), val_b = get_field(b);
+		struct hist_entry *pair;
+
+		list_for_each_entry(pair, &a->pairs.head, pairs.node)
+			val_a += get_field(pair);
+		list_for_each_entry(pair, &b->pairs.head, pairs.node)
+			val_b += get_field(pair);
+
+		ret = field_cmp(val_a, val_b);
+		if (ret)
+			return ret;
+		/* fall through to per-member tiebreaker */
+	}
 
 	ret = field_cmp(get_field(a), get_field(b));
 	if (ret || !symbol_conf.event_group)
diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
index 1a238b245b3a0..4140f998e8a9b 100644
--- a/tools/perf/util/evlist.c
+++ b/tools/perf/util/evlist.c
@@ -142,6 +142,49 @@ struct evlist *evlist__new_default(const struct target *target, bool sample_call
 	return NULL;
 }
 
+bool evlist__can_merge_hybrid(struct evlist *evlist)
+{
+	struct evsel *pos, *other;
+	u64 config = 0;
+	unsigned int nr = 0;
+	bool first = true;
+	int nr_core_pmus;
+
+	nr_core_pmus = perf_pmus__num_core_pmus();
+	if (nr_core_pmus <= 1)
+		return false;
+
+	evlist__for_each_entry(evlist, pos) {
+		if (evsel__is_dummy_event(pos))
+			continue;
+
+		/* Initial support is for legacy hardware events, such as cycles. */
+		if (!pos->pmu || !pos->pmu->is_core ||
+		    pos->core.attr.type != PERF_TYPE_HARDWARE)
+			return false;
+
+		if (first) {
+			/*
+			 * Filter out the PMU bits. May need something else
+			 * for other types.
+			 */
+			config = pos->core.attr.config & UINT32_MAX;
+			first = false;
+		} else if ((pos->core.attr.config & UINT32_MAX) != config) {
+			return false;
+		}
+
+		evlist__for_each_entry(evlist, other) {
+			if (other != pos && !evsel__is_dummy_event(other) &&
+			    other->pmu == pos->pmu)
+				return false;
+		}
+
+		nr++;
+	}
+	return !first && nr == (unsigned int)nr_core_pmus;
+}
+
 struct evlist *evlist__new_dummy(void)
 {
 	struct evlist *evlist = evlist__new();
diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h
index e507f5f20ef61..ad0e6e7399d2a 100644
--- a/tools/perf/util/evlist.h
+++ b/tools/perf/util/evlist.h
@@ -105,6 +105,7 @@ struct evsel_str_handler {
 
 struct evlist *evlist__new(void);
 struct evlist *evlist__new_default(const struct target *target, bool sample_callchains);
+bool evlist__can_merge_hybrid(struct evlist *evlist);
 struct evlist *evlist__new_dummy(void);
 void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus,
 		  struct perf_thread_map *threads);
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h
index b830cbe7f95bf..a9dd423ded5e1 100644
--- a/tools/perf/util/hist.h
+++ b/tools/perf/util/hist.h
@@ -130,6 +130,7 @@ struct hists {
 	struct hists_stats	stats;
 	u64			event_stream;
 	u16			col_len[HISTC_NR_COLS];
+	bool			merge_entries;
 	bool			has_callchains;
 	int			socket_filter;
 	struct perf_hpp_list	*hpp_list;
@@ -435,14 +436,26 @@ int hists__unlink(struct hists *hists);
 
 static inline float hist_entry__get_percent_limit(struct hist_entry *he)
 {
+	struct hist_entry *pair;
 	u64 period = he->stat.period;
 	u64 total_period = hists__total_period(he->hists);
 
+	if (he->hists->merge_entries) {
+		list_for_each_entry(pair, &he->pairs.head, pairs.node) {
+			period += pair->stat.period;
+			total_period += hists__total_period(pair->hists);
+		}
+	}
+
 	if (unlikely(total_period == 0))
 		return 0;
 
-	if (symbol_conf.cumulate_callchain)
+	if (symbol_conf.cumulate_callchain) {
 		period = he->stat_acc->period;
+		if (he->hists->merge_entries)
+			list_for_each_entry(pair, &he->pairs.head, pairs.node)
+				period += pair->stat_acc->period;
+	}
 
 	return period * 100.0 / total_period;
 }
diff --git a/tools/perf/util/top.c b/tools/perf/util/top.c
index b06e10a116bb3..b7ae51fbc9541 100644
--- a/tools/perf/util/top.c
+++ b/tools/perf/util/top.c
@@ -78,7 +78,13 @@ size_t perf_top__header_snprintf(struct perf_top *top, char *bf, size_t size)
 				opts->freq ? "Hz" : "");
 	}
 
-	ret += SNPRINTF(bf + ret, size - ret, "%s", evsel__name(top->sym_evsel));
+	if (evsel__is_group_event(top->sym_evsel)) {
+		char buf[256];
+		evsel__group_desc(top->sym_evsel, buf, sizeof(buf));
+		ret += SNPRINTF(bf + ret, size - ret, "%s", buf);
+	} else {
+		ret += SNPRINTF(bf + ret, size - ret, "%s", evsel__name(top->sym_evsel));
+	}
 
 	ret += SNPRINTF(bf + ret, size - ret, "], ");
 
diff --git a/tools/perf/util/top.h b/tools/perf/util/top.h
index 04ff926846be0..b42e23066fc0d 100644
--- a/tools/perf/util/top.h
+++ b/tools/perf/util/top.h
@@ -32,6 +32,7 @@ struct perf_top {
 	u64		   guest_us_samples, guest_kernel_samples;
 	int		   print_entries, count_filter, delay_secs;
 	int		   max_stack;
+	bool		   hybrid_merge;
 	bool		   hide_kernel_symbols, hide_user_symbols, zero;
 #ifdef HAVE_SLANG_SUPPORT
 	bool		   use_tui;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-08-13 13:57 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-13 13:25 [PATCH v1] perf top: Merge hybrid common events Andi Kleen
2026-08-13 13:57 ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox