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 6/7] perf ui hist: Format group headers iteratively based on proportional visual allocations
Date: Sun, 23 Aug 2026 23:37:43 -0700	[thread overview]
Message-ID: <20260824063744.1533837-7-irogers@google.com> (raw)
In-Reply-To: <20260824063744.1533837-1-irogers@google.com>

Forcefully calculate proportional visual allocations only natively for
the 'Total' merge target header instead of uniformly ballooning array
spacings. Unpack strings directly into the primary column width fn.

Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Antigravity:gemini-3.1-pro
---
 tools/perf/ui/hist.c     | 86 ++++++++++++++++++++++++++++++++--------
 tools/perf/util/evlist.c |  9 +++--
 tools/perf/util/hist.h   |  8 +++-
 3 files changed, 82 insertions(+), 21 deletions(-)

diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c
index 09f0baa808e0..02c188100b09 100644
--- a/tools/perf/ui/hist.c
+++ b/tools/perf/ui/hist.c
@@ -57,8 +57,9 @@ struct hpp_fmt_value {
 };
 
 static int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
-		      hpp_field_fn get_field, const char *fmt, int len,
-		      hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype)
+		      hpp_field_fn get_field, const char *fmtstr, int len,
+		      hpp_snprint_fn print_fn, enum perf_hpp_fmt_type fmtype,
+		      struct perf_hpp_fmt *fmt)
 {
 	int ret = 0;
 	struct hists *hists = he->hists;
@@ -98,14 +99,44 @@ static int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
 		}
 	}
 
-	for (i = 0; i < nr_members; i++) {
-		if (symbol_conf.skip_empty &&
-		    values[i].hists->stats.nr_samples == 0)
-			continue;
+	if (he->hists->merge_entries) {
+		u64 total_val = 0;
+		u64 total_samples = 0;
+		u64 total_period = 0;
+
+		for (i = 0; i < nr_members; i++) {
+			total_val += values[i].val;
+			total_samples += values[i].samples;
+			total_period += fmtype == PERF_HPP_FMT_TYPE__PERCENT ?
+					hists__total_period(values[i].hists) :
+					hists__total_latency(values[i].hists);
+		}
+
+		if (fmtype == PERF_HPP_FMT_TYPE__PERCENT || fmtype == PERF_HPP_FMT_TYPE__LATENCY) {
+			double percent = 0.0;
+
+			if (total_period)
+				percent = 100.0 * total_val / total_period;
+			ret += hpp__call_print_fn(hpp, print_fn, fmtstr, len, percent);
+		} else if (fmtype == PERF_HPP_FMT_TYPE__AVERAGE) {
+			double avg = total_samples ? (1.0 * total_val / total_samples) : 0;
 
-		ret += __hpp__fmt_print(hpp, values[i].hists, values[i].val,
-					values[i].samples, fmt, len,
-					print_fn, fmtype);
+			ret += hpp__call_print_fn(hpp, print_fn, fmtstr, len, avg);
+		} else {
+			ret += hpp__call_print_fn(hpp, print_fn, fmtstr, len, total_val);
+		}
+	}
+
+	if (!he->hists->merge_entries || &fmt->list == he->hists->hpp_list->fields.next) {
+		for (i = 0; i < nr_members; i++) {
+			if (symbol_conf.skip_empty &&
+			    values[i].hists->stats.nr_samples == 0)
+				continue;
+
+			ret += __hpp__fmt_print(hpp, values[i].hists, values[i].val,
+							values[i].samples, fmtstr, len,
+						print_fn, fmtype);
+		}
 	}
 
 	free(values);
@@ -129,7 +160,7 @@ int hpp__fmt(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
 
 	if (symbol_conf.field_sep) {
 		return __hpp__fmt(hpp, he, get_field, fmtstr, 1,
-				  print_fn, fmtype);
+				  print_fn, fmtype, fmt);
 	}
 
 	if (fmtype == PERF_HPP_FMT_TYPE__PERCENT || fmtype == PERF_HPP_FMT_TYPE__LATENCY)
@@ -137,7 +168,7 @@ int hpp__fmt(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
 	else
 		len -= 1;
 
-	return  __hpp__fmt(hpp, he, get_field, fmtstr, len, print_fn, fmtype);
+	return  __hpp__fmt(hpp, he, get_field, fmtstr, len, print_fn, fmtype, fmt);
 }
 
 int hpp__fmt_acc(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
@@ -399,10 +430,16 @@ static int hpp__width_fn(struct perf_hpp_fmt *fmt,
 		int nr = 0;
 		struct evsel *pos;
 
-		for_each_group_evsel(pos, evsel) {
-			if (!symbol_conf.skip_empty ||
-			    evsel__hists(pos)->stats.nr_samples)
-				nr++;
+		if (hists->merge_entries && &fmt->list != hists->hpp_list->fields.next) {
+			nr = 1;
+		} else {
+			for_each_group_evsel(pos, evsel) {
+				if (!symbol_conf.skip_empty ||
+				    evsel__hists(pos)->stats.nr_samples)
+					nr++;
+			}
+			if (hists->merge_entries && &fmt->list == hists->hpp_list->fields.next)
+				nr++; /* Add 1 extra unit of width generically for the 'Total' */
 		}
 
 		len = max(len, nr * fmt->len);
@@ -421,8 +458,25 @@ static int hpp__header_fn(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
 	int len = hpp__width_fn(fmt, hpp, hists);
 	const char *hdr = "";
 
-	if (line == hists->hpp_list->nr_header_lines - 1)
+	if (line == hists->hpp_list->nr_header_lines - 1) {
 		hdr = fmt->name;
+		if (hists->merge_entries && &fmt->list == hists->hpp_list->fields.next) {
+			char buf[1024];
+			int w = 0;
+			int f_len = fmt->user_len ?: fmt->len;
+			struct evsel *pos, *evsel = hists_to_evsel(hists);
+
+			w += scnprintf(buf + w, sizeof(buf) - w, "%*.*s", f_len, f_len, fmt->name);
+			for_each_group_evsel(pos, evsel) {
+				if (symbol_conf.skip_empty &&
+			    evsel__hists(pos)->stats.nr_samples == 0)
+					continue;
+				w += scnprintf(buf + w, sizeof(buf) - w, " %*.*s",
+					       f_len - 1, f_len - 1, evsel__name(pos));
+			}
+			return scnprintf(hpp->buf, hpp->size, "%*s", len, buf);
+		}
+	}
 
 	return scnprintf(hpp->buf, hpp->size, "%*s", len, hdr);
 }
diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
index 431759e05e26..4c928ef5c985 100644
--- a/tools/perf/util/evlist.c
+++ b/tools/perf/util/evlist.c
@@ -324,10 +324,13 @@ void evlist__merge_hybrid(struct evlist *evlist, bool refresh_hists)
 	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 (!pos->group_name)
+				pos->group_name = strdup("Merged hybrid events");
 		}
 	}
 
@@ -345,12 +348,10 @@ void evlist__merge_hybrid(struct evlist *evlist, bool refresh_hists)
 		}
 	}
 
-	/* Now that links are formed, safely resort the active tree so the UI renders accurately */
+	/* Resort the output dynamically since group parsing bypassed this before */
 	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)
+			if (pos->core.leader == &pos->core && pos->core.nr_members > 1)
 				hists__output_resort(evsel__hists(pos), NULL);
 		}
 	}
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h
index 6cb7059e8405..5a0cfa8ea264 100644
--- a/tools/perf/util/hist.h
+++ b/tools/perf/util/hist.h
@@ -339,7 +339,13 @@ static inline struct hist_entry *hist_entry__next_pair(struct hist_entry *he)
 static inline void hist_entry__add_pair(struct hist_entry *pair,
 					struct hist_entry *he)
 {
-	list_add_tail(&pair->pairs.node, &he->pairs.head);
+	struct list_head *pos;
+
+	list_for_each(pos, &he->pairs.head) {
+		if (pos == &pair->pairs.node)
+			return; /* Already paired */
+	}
+	list_move_tail(&pair->pairs.node, &he->pairs.head);
 }
 
 struct hist_entry *hists__add_entry(struct hists *hists,
-- 
2.55.0.766.g2966f0265a-goog


  parent reply	other threads:[~2026-08-24  6:38 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   ` [PATCH v1 1/7] perf evlist: Implement evlist__can_merge_hybrid using first_wildcard_match Ian Rogers
2026-08-24  6:53     ` 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   ` Ian Rogers [this message]
2026-08-24  6:53     ` [PATCH v1 6/7] perf ui hist: Format group headers iteratively based on proportional visual allocations 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-7-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.