All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ian Rogers <irogers@google.com>
To: andi@firstfloor.org
Cc: acme@kernel.org, ak@kernel.org, ak@linux.intel.com,
	 linux-perf-users@vger.kernel.org, namhyung@kernel.org,
	 Ian Rogers <irogers@google.com>
Subject: [PATCH v1 1/7] perf evlist: Implement evlist__can_merge_hybrid using first_wildcard_match
Date: Sun, 23 Aug 2026 23:37:38 -0700	[thread overview]
Message-ID: <20260824063744.1533837-2-irogers@google.com> (raw)
In-Reply-To: <20260824063744.1533837-1-irogers@google.com>

Add logic to dynamically identify mergeable events spawned from the same
wildcard alias via first_wildcard_match, breaking reliance on hardcoded
PMU metrics or type IDs.

Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
 tools/perf/util/evlist.c      | 208 ++++++++++++++++++++++++++++++++++
 tools/perf/util/evlist.h      |   2 +
 tools/perf/util/evsel.c       |   2 +
 tools/perf/util/hist.h        |   1 +
 tools/perf/util/symbol_conf.h |   1 +
 5 files changed, 214 insertions(+)

diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
index c3d784727810..431759e05e26 100644
--- a/tools/perf/util/evlist.c
+++ b/tools/perf/util/evlist.c
@@ -1,4 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0-only
+#include "util/pmu.h"
 /*
  * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
  *
@@ -6,7 +7,9 @@
  * copyright notes.
  */
 #include "evlist.h"
+#include "hist.h"
 
+#include <stdio.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <signal.h>
@@ -125,12 +128,21 @@ struct evlist *evlist__new_default(const struct target *target, bool sample_call
 		if (err)
 			goto out_err;
 	} else {
+		struct evsel *leader = NULL;
 		while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
 			snprintf(buf, sizeof(buf), "%s/cycles/%s", pmu->name,
 				can_profile_kernel ? "P" : "Pu");
 			err = parse_event(evlist, buf);
 			if (err)
 				goto out_err;
+			if (!leader)
+				leader = evlist__last(evlist);
+			else {
+				struct evsel *last = evlist__last(evlist);
+
+				if (last != leader)
+					last->first_wildcard_match = leader;
+			}
 		}
 	}
 
@@ -148,6 +160,202 @@ struct evlist *evlist__new_default(const struct target *target, bool sample_call
 	return NULL;
 }
 
+
+/**
+ * is_pmu_core_len - Check if a given string prefix matches a core PMU name.
+ * @name: The string to check.
+ * @len: The length of the PMU name prefix in the string.
+ *
+ * This function is used instead of the global `is_pmu_core()` from pmu.h
+ * because it operates natively on substrings without requiring null-termination
+ * (e.g. strndup allocations) when parsing event names like "cpu_core/cycles/".
+ */
+static bool is_pmu_core_len(const char *name, size_t len)
+{
+	struct perf_pmu *pmu = NULL;
+
+	while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+		if (!strncmp(name, pmu->name, len) && strlen(pmu->name) == len)
+			return true;
+	}
+	return false;
+}
+
+/*
+ * evlist__can_merge_hybrid - check if hybrid events can be merged.
+ * @evlist: The evlist to check.
+ *
+ * This code is valid for perf record, top, etc. as the event parsing
+ * will set first_wildcard_match. The perf.data case (e.g. perf report)
+ * recomputes the first_wildcard_match in the case there are none by
+ * falling back to string matches only in the case of core events on
+ * hybrid systems.
+ */
+bool evlist__can_merge_hybrid(struct evlist *evlist)
+{
+	struct evsel *pos;
+	unsigned int nr = 0;
+	bool has_wildcard = false;
+
+	evlist__for_each_entry(evlist, pos) {
+		if (evsel__is_dummy_event(pos))
+			continue;
+		if (pos->first_wildcard_match)
+			has_wildcard = true;
+		nr++;
+	}
+
+	if (!has_wildcard) {
+		evlist__for_each_entry(evlist, pos) {
+			const char *pos_name;
+			char *pos_match;
+			struct evsel *peer;
+
+			if (evsel__is_dummy_event(pos) || pos->first_wildcard_match)
+				continue;
+
+			pos_name = evsel__name(pos);
+			pos_match = strchr(pos_name, '/');
+			if (!pos_match)
+				continue;
+
+			/* If evsel->core.is_pmu_core missing in report, fallback to prefix */
+			if (!evsel__is_hybrid(pos)) {
+				if (!is_pmu_core_len(pos_name, pos_match - pos_name) ||
+				    perf_pmus__num_core_pmus() <= 1)
+					continue;
+			}
+
+			peer = pos;
+			list_for_each_entry_continue(peer, &evlist->core.entries, core.node) {
+				const char *peer_name;
+				char *peer_match;
+
+				if (evsel__is_dummy_event(peer) || peer->first_wildcard_match)
+					continue;
+
+				peer_name = evsel__name(peer);
+				peer_match = strchr(peer_name, '/');
+				if (!peer_match)
+					continue;
+
+				if (!evsel__is_hybrid(peer)) {
+					if (!is_pmu_core_len(peer_name, peer_match - peer_name) ||
+					    perf_pmus__num_core_pmus() <= 1)
+						continue;
+				}
+
+				if (!strcmp(pos_match, peer_match)) {
+					peer->first_wildcard_match = pos;
+					has_wildcard = true;
+				}
+			}
+		}
+	}
+
+	return has_wildcard && (nr > 1);
+}
+
+/*
+ * evlist__merge_hybrid - group hybrid events logically together.
+ * @evlist: The evlist containing events to merge.
+ *
+ * Iterates through the evlist and logically merges associated hybrid events
+ * by assigning their first_wildcard_match as their core group leader,
+ * modifying their presentation into a single merged histogram view.
+ */
+void evlist__merge_hybrid(struct evlist *evlist, bool refresh_hists)
+{
+	struct evsel *pos, *tmp;
+	int idx = 0;
+
+	evlist__for_each_entry_safe(evlist, tmp, pos) {
+		if (evsel__is_dummy_event(pos))
+			continue;
+
+		if (pos->first_wildcard_match) {
+			struct evsel *leader = evsel__leader(pos->first_wildcard_match);
+			struct evsel *old_leader = evsel__leader(pos);
+
+			if (old_leader != leader) {
+				struct evsel *member;
+
+				if (old_leader != pos)
+					old_leader->core.nr_members--;
+				pos->core.leader = &leader->core;
+				/* Base is 1 to natively represent the leader */
+				if (leader->core.nr_members == 0)
+					leader->core.nr_members = 1;
+				leader->core.nr_members++;
+
+				/* Assign stranded members to the new leader as well */
+				for_each_group_member(member, pos) {
+					if (!member->first_wildcard_match) {
+						member->core.leader = &leader->core;
+						leader->core.nr_members++;
+					}
+				}
+			}
+		}
+	}
+
+	{
+		struct list_head new_list;
+		struct evsel *member, *mtmp;
+
+		INIT_LIST_HEAD(&new_list);
+
+		while (!list_empty(&evlist->core.entries)) {
+			pos = list_first_entry(&evlist->core.entries, struct evsel, core.node);
+			list_move_tail(&pos->core.node, &new_list);
+
+			list_for_each_entry_safe(member, mtmp, &evlist->core.entries, core.node) {
+				if (member->core.leader == &pos->core)
+					list_move_tail(&member->core.node, &new_list);
+			}
+		}
+		list_splice_init(&new_list, &evlist->core.entries);
+	}
+
+	evlist__for_each_entry(evlist, pos)
+		pos->core.idx = idx++;
+
+	/* Set merge_entries flag on leaders */
+	evlist__for_each_entry(evlist, pos) {
+		if (evsel__is_dummy_event(pos))
+			continue;
+		if (pos->core.leader == &pos->core && pos->core.nr_members > 1) {
+			evsel__hists(pos)->merge_entries = true;
+			symbol_conf.event_group = true;
+			symbol_conf.hybrid_merge = true;
+		}
+	}
+
+	if (!refresh_hists)
+		return;
+
+	evlist__for_each_entry(evlist, pos) {
+		/* Match histograms dynamically since parsing happened before group toggling */
+		if (symbol_conf.event_group && !evsel__is_group_leader(pos)) {
+			struct hists *leader_hists = evsel__hists(evsel__leader(pos));
+			struct hists *hists = evsel__hists(pos);
+
+			hists__match(leader_hists, hists);
+			hists__link(leader_hists, hists);
+		}
+	}
+
+	/* Now that links are formed, safely resort the active tree so the UI renders accurately */
+	if (symbol_conf.event_group) {
+		evlist__for_each_entry(evlist, pos) {
+			if (evsel__is_dummy_event(pos) || !evsel__is_group_leader(pos))
+				continue;
+			if (pos->core.nr_members > 1)
+				hists__output_resort(evsel__hists(pos), NULL);
+		}
+	}
+}
+
 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 838e263b76f3..12f3fd7dad9b 100644
--- a/tools/perf/util/evlist.h
+++ b/tools/perf/util/evlist.h
@@ -331,6 +331,8 @@ static inline void evlist__set_selected(struct evlist *evlist, struct evsel *evs
 
 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);
+void evlist__merge_hybrid(struct evlist *evlist, bool refresh_hists);
 struct evlist *evlist__new_dummy(void);
 struct evlist *evlist__get(struct evlist *evlist);
 void evlist__put(struct evlist *evlist);
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index d4cb455f4a7d..3f56a0e6f9d6 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -2052,6 +2052,8 @@ static void evsel__exit(struct evsel *evsel)
 	evsel__free_config_terms(evsel);
 	cgroup__put(evsel->cgrp);
 	perf_evsel__exit(&evsel->core);
+	if (evsel->first_wildcard_match)
+		evsel->first_wildcard_match = NULL;
 	zfree(&evsel->group_name);
 	zfree(&evsel->name);
 #ifdef HAVE_LIBTRACEEVENT
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h
index b830cbe7f95b..ea79628bbc6b 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;
diff --git a/tools/perf/util/symbol_conf.h b/tools/perf/util/symbol_conf.h
index 0dee5aa6a534..2bdd96fe886f 100644
--- a/tools/perf/util/symbol_conf.h
+++ b/tools/perf/util/symbol_conf.h
@@ -28,6 +28,7 @@ enum a2l_style {
 #define MAX_A2L_STYLE (A2L_STYLE_CMD + 1)
 
 struct symbol_conf {
+	bool		hybrid_merge;
 	bool		nanosecs;
 	unsigned short	priv_size;
 	bool		try_vmlinux_path,
-- 
2.55.0.766.g2966f0265a-goog


  reply	other threads:[~2026-08-24  6:37 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-13 13:25 [PATCH v1] perf top: Merge hybrid common events Andi Kleen
2026-08-13 13:57 ` sashiko-bot
2026-08-17 19:40 ` Ian Rogers
2026-08-18 17:29   ` Andi Kleen
2026-08-19  2:58     ` Ian Rogers
2026-08-19  3:34       ` Andi Kleen
2026-08-19  4:16         ` Ian Rogers
2026-08-19 16:11           ` Andi Kleen
2026-08-19 17:58             ` Ian Rogers
2026-08-19 18:25               ` Andi Kleen
2026-08-19 22:18                 ` Ian Rogers
2026-08-24  6:37 ` [PATCH v1 0/7] perf ui: Implement hybrid event merging for heterogeneous systems Ian Rogers
2026-08-24  6:37   ` Ian Rogers [this message]
2026-08-24  6:53     ` [PATCH v1 1/7] perf evlist: Implement evlist__can_merge_hybrid using first_wildcard_match sashiko-bot
2026-08-24 22:49     ` Andi Kleen
2026-08-25  4:01       ` Ian Rogers
2026-08-24  6:37   ` [PATCH v1 2/7] perf ui hist: Add support for aggregated total_period and merging entries cleanly Ian Rogers
2026-08-24  6:53     ` sashiko-bot
2026-08-24  6:37   ` [PATCH v1 3/7] perf ui browsers: Implement interactive 'M' keystroke to toggle hybrid event merging Ian Rogers
2026-08-24  6:49     ` sashiko-bot
2026-08-24  6:37   ` [PATCH v1 4/7] perf tools: Expose opt-in --hybrid-merge Ian Rogers
2026-08-24  6:52     ` sashiko-bot
2026-08-24 22:40     ` Andi Kleen
2026-08-25  3:33       ` Ian Rogers
2026-08-25 22:19         ` Arnaldo Carvalho de Melo
2026-08-24  6:37   ` [PATCH v1 5/7] perf Documentation: Add tip for hybrid event merging Ian Rogers
2026-08-24  6:40     ` sashiko-bot
2026-08-24  6:37   ` [PATCH v1 6/7] perf ui hist: Format group headers iteratively based on proportional visual allocations Ian Rogers
2026-08-24  6:53     ` sashiko-bot
2026-08-24  6:37   ` [PATCH v1 7/7] perf test: Expand top tests for --hybrid-merge Ian Rogers
2026-08-24  6:55     ` sashiko-bot

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=20260824063744.1533837-2-irogers@google.com \
    --to=irogers@google.com \
    --cc=acme@kernel.org \
    --cc=ak@kernel.org \
    --cc=ak@linux.intel.com \
    --cc=andi@firstfloor.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=namhyung@kernel.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.