* [PATCH v9 0/9] perf c2c: add a function view
@ 2026-08-17 9:46 Jiebin Sun
2026-08-17 9:46 ` [PATCH v9 1/9] perf c2c: extract shared data structures into util/c2c.h Jiebin Sun
` (8 more replies)
0 siblings, 9 replies; 30+ messages in thread
From: Jiebin Sun @ 2026-08-17 9:46 UTC (permalink / raw)
To: namhyung
Cc: acme, mingo, peterz, adrian.hunter, alexander.shishkin, irogers,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, jiebin.sun, linux-perf-users,
linux-kernel
The perf c2c cacheline view identifies hot shared lines, but it can be
difficult to see which functions account for contention across those lines.
This series adds a function-oriented view that groups the existing data as:
read-side function -> contending writer -> shared cacheline
Pressing TAB switches between the cacheline and function views. Existing
cacheline-view data and operations remain unchanged.
v1: https://lore.kernel.org/linux-perf-users/20260626070355.1556721-1-jiebin.sun@intel.com/
v2: https://lore.kernel.org/linux-perf-users/20260710084247.3576706-1-jiebin.sun@intel.com/
v3: https://lore.kernel.org/linux-perf-users/20260717020530.1645123-1-jiebin.sun@intel.com/
v4: https://lore.kernel.org/linux-perf-users/20260724095842.995920-1-jiebin.sun@intel.com/
v5: https://lore.kernel.org/linux-perf-users/20260730090521.2206375-1-jiebin.sun@intel.com/
v6: https://lore.kernel.org/linux-perf-users/20260803035220.172022-1-jiebin.sun@intel.com/
v7: https://lore.kernel.org/linux-perf-users/20260806074217.2846596-1-jiebin.sun@intel.com/
v8: https://lore.kernel.org/linux-perf-users/20260810052647.588867-1-jiebin.sun@intel.com/
Changes since v8
================
- Rebase onto perf-tools-next at 6ae6fb96ccd4.
- Move the function-view hierarchy construction, aggregation, sorting,
and column formatting to util/c2c-function.c, as suggested by Namhyung.
Keep only the TUI browser, key handling, and cacheline detail action in
ui/browsers/c2c-function.c.
- Make the common model validate inputs before replacing existing state
and keep its entries callchain-free during construction and teardown,
independent of the caller's current callchain setting.
- Simplify three rbtree node-to-entry conversions by using rb_entry()
directly with the nested hist_entry rb_node, as suggested by Namhyung.
- Clarify that Cycles % uses the surviving level-1 entries after pruning.
No functional change.
Changes since v7
================
- Keep c2c-function.o in libperf-ui.a and drop the v7 tools/perf/Build
change, as Namhyung suggested. builtin-c2c.c now passes the cacheline
histograms, coalescing fields, symbol_full flag, and cacheline detail
callback through struct c2c_function_view_args. The browser no longer
references command-private state.
- Move the shared c2c types and the two column-formatting callbacks into
new util/c2c.h and util/c2c.c. Keep struct perf_c2c, the command
instance, and perf_c2c__browse_cacheline() private to builtin-c2c.c.
- Rebase onto perf-tools-next at bf10e6ee2ac3.
Changes since v6
================
- Link c2c-function.o directly into perf under CONFIG_SLANG instead of
including it in libperf-ui.a. The browser depends on state and callbacks
provided by builtin-c2c.o, which is not linked into python/perf.so,
while libperf-ui.a is linked under --whole-archive. This fixes the
unresolved symbols seen when importing the Python module and keeps the
TUI-only object out of NO_SLANG builds.
- Rebase onto perf-tools-next at d74fb8c4c87f.
Changes since v5
================
- Give the function-view children their own references instead of
borrowing them, as suggested by Namhyung. They set ops and take refs
via map_symbol__copy() and thread__get(), so hist_entry__delete()
frees them; the custom child destructor and the
WARN_ON_ONCE(report_hierarchy) guard are gone.
- Add NO_SLANG stubs for the two TUI entry points declared in c2c.h,
matching how util/hist.h declares its own.
- Spell out in the symbol renderer why the length returned by
se_snprintf() cannot exceed the cell.
- Rebase onto perf-tools-next at d04ef71492fa.
No intended change to the rendered output, cacheline-view data, existing
cacheline operations, or --stdio output.
Changes since v4
================
- Rebase onto perf-tools-next at 0d40fedcd1b9.
- Require `iaddr` in --coalesce before entering the function view;
without it, coalesced samples cannot be reliably attributed.
- Reuse each source cacheline's existing index as the level-3 identity,
so same-address sources no longer merge and 'd' finds the original
entry, without adding fields to the shared C2C structures.
- Keep same-function reader/writer pairs, which the coalesced view cannot
reliably tell apart from cross-thread contention.
- Robustness and cleanup: build the hierarchy only from filtered
cachelines with a lightweight entry allocator; explicit
mem_info__new() handling; a local bound in the symbol renderer; a
NULL-map guard; deterministic tie-breaks; function-view-local row
recounting; Ctrl-C as an exit key; and expanded man-page docs.
Changes since v3
================
Reworked the function view into a true function-centric hierarchy, as
discussed with Namhyung:
- Level 1 is the read-side function itself, aggregating all of its code
addresses into one entry and sorting by Cycles %.
- Level 2 is the contending function that writes the shared lines,
aggregated across cachelines and sorted by store count. Level 3 is the
specific cachelines.
- Dedicated code-address and offset columns were removed from the main
view. Verbose function formatting can still include a representative
code address, and full code-address information remains available in
the per-cacheline detail view ('d').
- Lower-level entries are indented like the normal perf report hierarchy
view, and entries with no contending writer are pruned.
- Reorganized the series from 14 mechanical patches into 9 logically
self-contained patches.
Changes since v2
================
- Prefix helpers exported from builtin-c2c.c with c2c_, as suggested by
Namhyung.
Changes since v1
================
- Add a worked man-page example, as suggested by Namhyung.
In the perf c2c TUI, press TAB in the cacheline view to switch to the
function view. It presents a three-level hierarchy:
Level 1: read-side function, sorted by Cycles %
Level 2: functions writing the shared lines, sorted by store count
Level 3: cachelines where the two functions contend
For example, with dequeue_pushable_task expanded to its level-2 writers and
two of those writers expanded to their level-3 cachelines:
Shared Data Functions Table (19 entries, sorted on Cycles %)
Cycles Store
% count Function / Contending function / Cacheline
----------------------------------------------------------------------
+ 35.67% 876 + [k] cpupri_set
+ 24.31% 424 + [k] pull_rt_task
- 16.53% 555 - [k] dequeue_pushable_task
145 - [k] pull_rt_task
145 0xff2d0082809da080
139 - [k] enqueue_pushable_task
70 0xff2d00a2071f9640
69 0xff2d0082809da000
A level-1 store count decomposes into its level-2 writers, and each writer's
count decomposes into its level-3 cachelines (here 145 = 145, 139 = 70 + 69).
Keys in the function view:
TAB/ESC/q/Ctrl-C return to the cacheline view
d show details for the selected level-3 cacheline
e / + expand / collapse the selected entry
? help
The common function-view model is in tools/perf/util/c2c-function.c. It
constructs the hierarchy from the existing cacheline histograms into
dedicated hists and provides the custom column formatters. The TUI front
end remains in tools/perf/ui/browsers/c2c-function.c.
Testing: every commit builds with and without SLANG support. In both
configurations python/perf.so has no unresolved symbols, the Python module
imports successfully, and the full series passes `perf test c2c` and
`perf test 'import perf'`. The cacheline-view --stdio output is
byte-identical to the base commit. A TUI smoke test entered the function
view and expanded its first level-1 entry; the rendered output was
identical to the existing reference screen after trimming trailing spaces.
Jiebin Sun (9):
perf c2c: extract shared data structures into util/c2c.h
perf c2c: add function view model skeleton
perf c2c: add column rendering for function view
perf c2c: add HPP list parsing for function view columns
perf c2c: add function view stats merge and memory management
perf c2c: add function view hierarchy entry creation
perf c2c: build and finalize the function view hierarchy
perf c2c: add function view browser UI and cacheline detail
perf c2c: document function view in perf-c2c man page
tools/perf/Documentation/perf-c2c.txt | 71 ++
tools/perf/builtin-c2c.c | 115 +-
tools/perf/ui/browsers/Build | 1 +
tools/perf/ui/browsers/c2c-function.c | 209 ++++
tools/perf/util/Build | 2 +
tools/perf/util/c2c-function.c | 1642 +++++++++++++++++++++++++
tools/perf/util/c2c.c | 21 +
tools/perf/util/c2c.h | 135 ++
8 files changed, 2094 insertions(+), 102 deletions(-)
create mode 100644 tools/perf/ui/browsers/c2c-function.c
create mode 100644 tools/perf/util/c2c-function.c
create mode 100644 tools/perf/util/c2c.c
create mode 100644 tools/perf/util/c2c.h
--
2.52.0
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH v9 1/9] perf c2c: extract shared data structures into util/c2c.h
2026-08-17 9:46 [PATCH v9 0/9] perf c2c: add a function view Jiebin Sun
@ 2026-08-17 9:46 ` Jiebin Sun
2026-08-17 9:59 ` sashiko-bot
2026-08-17 20:34 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 2/9] perf c2c: add function view model skeleton Jiebin Sun
` (7 subsequent siblings)
8 siblings, 2 replies; 30+ messages in thread
From: Jiebin Sun @ 2026-08-17 9:46 UTC (permalink / raw)
To: namhyung
Cc: acme, mingo, peterz, adrian.hunter, alexander.shishkin, irogers,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, jiebin.sun, linux-perf-users,
linux-kernel
The function browser belongs in libperf-ui.a, but that archive is also
linked into python/perf.so, where builtin command objects are unavailable.
The browser therefore cannot depend on types or callbacks owned by
builtin-c2c.c.
Move c2c_hists, compute_stats, c2c_hist_entry, and the shared column
formatting definitions from builtin-c2c.c to a new util/c2c.h. Move
c2c_fmt_free() and c2c_fmt_equal() to a new util/c2c.c.
Keep struct perf_c2c, the command instance, and
perf_c2c__browse_cacheline() private to builtin-c2c.c.
No functional change.
Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
---
tools/perf/builtin-c2c.c | 105 ++-------------------------------------
tools/perf/util/Build | 1 +
tools/perf/util/c2c.c | 21 ++++++++
tools/perf/util/c2c.h | 101 +++++++++++++++++++++++++++++++++++++
4 files changed, 126 insertions(+), 102 deletions(-)
create mode 100644 tools/perf/util/c2c.c
create mode 100644 tools/perf/util/c2c.h
diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
index bc16a57e0927..16b00a36fdfc 100644
--- a/tools/perf/builtin-c2c.c
+++ b/tools/perf/builtin-c2c.c
@@ -52,46 +52,10 @@
#include "ui/progress.h"
#include "ui/ui.h"
#include "util/annotate.h"
+#include "util/c2c.h"
#include "util/symbol.h"
#include "util/util.h"
-struct c2c_hists {
- struct hists hists;
- struct perf_hpp_list list;
- struct c2c_stats stats;
-};
-
-struct compute_stats {
- struct stats lcl_hitm;
- struct stats rmt_hitm;
- struct stats lcl_peer;
- struct stats rmt_peer;
- struct stats load;
-};
-
-struct c2c_hist_entry {
- struct c2c_hists *hists;
- struct evsel *evsel;
- struct c2c_stats stats;
- unsigned long *cpuset;
- unsigned long *nodeset;
- struct c2c_stats *node_stats;
- unsigned int cacheline_idx;
-
- struct compute_stats cstats;
-
- unsigned long paddr;
- unsigned long paddr_cnt;
- bool paddr_zero;
- char *nodestr;
-
- /*
- * must be at the end,
- * because of its callchain dynamic entry
- */
- struct hist_entry he;
-};
-
static char const *coalesce_default = "iaddr";
struct perf_c2c {
@@ -460,36 +424,6 @@ static const char * const __usage_report[] = {
static const char * const *report_c2c_usage = __usage_report;
-#define C2C_HEADER_MAX 2
-
-struct c2c_header {
- struct {
- const char *text;
- int span;
- } line[C2C_HEADER_MAX];
-};
-
-struct c2c_dimension {
- struct c2c_header header;
- const char *name;
- int width;
- struct sort_entry *se;
-
- int64_t (*cmp)(struct perf_hpp_fmt *fmt,
- struct hist_entry *, struct hist_entry *);
- int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
- struct hist_entry *he);
- int (*color)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
- struct hist_entry *he);
-};
-
-struct c2c_fmt {
- struct perf_hpp_fmt fmt;
- struct c2c_dimension *dim;
-};
-
-#define SYMBOL_WIDTH 30
-
static struct c2c_dimension dim_symbol;
static struct c2c_dimension dim_srcline;
@@ -1391,23 +1325,6 @@ cl_idx_empty_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
return scnprintf(hpp->buf, hpp->size, "%*s", width, "");
}
-#define HEADER_LOW(__h) \
- { \
- .line[1] = { \
- .text = __h, \
- }, \
- }
-
-#define HEADER_BOTH(__h0, __h1) \
- { \
- .line[0] = { \
- .text = __h0, \
- }, \
- .line[1] = { \
- .text = __h1, \
- }, \
- }
-
#define HEADER_SPAN(__h0, __h1, __s) \
{ \
.line[0] = { \
@@ -1930,22 +1847,6 @@ static struct c2c_dimension *dimensions[] = {
NULL,
};
-static void fmt_free(struct perf_hpp_fmt *fmt)
-{
- struct c2c_fmt *c2c_fmt;
-
- c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
- free(c2c_fmt);
-}
-
-static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
-{
- struct c2c_fmt *c2c_a = container_of(a, struct c2c_fmt, fmt);
- struct c2c_fmt *c2c_b = container_of(b, struct c2c_fmt, fmt);
-
- return c2c_a->dim == c2c_b->dim;
-}
-
static struct c2c_dimension *get_dimension(const char *name)
{
unsigned int i;
@@ -2023,8 +1924,8 @@ static struct c2c_fmt *get_format(const char *name)
fmt->header = c2c_header;
fmt->width = c2c_width;
fmt->collapse = dim->se ? c2c_se_collapse : dim->cmp;
- fmt->equal = fmt_equal;
- fmt->free = fmt_free;
+ fmt->equal = c2c_fmt_equal;
+ fmt->free = c2c_fmt_free;
return c2c_fmt;
}
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 330311cac550..1dfd92cbe3b7 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -11,6 +11,7 @@ perf-util-y += blake2s.o
perf-util-y += block-info.o
perf-util-y += block-range.o
perf-util-y += build-id.o
+perf-util-y += c2c.o
perf-util-y += cacheline.o
perf-util-$(CONFIG_LIBCAPSTONE) += capstone.o
perf-util-y += config.o
diff --git a/tools/perf/util/c2c.c b/tools/perf/util/c2c.c
new file mode 100644
index 000000000000..58c7342dff46
--- /dev/null
+++ b/tools/perf/util/c2c.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <stdlib.h>
+#include <linux/kernel.h>
+#include "hist.h"
+#include "c2c.h"
+
+void c2c_fmt_free(struct perf_hpp_fmt *fmt)
+{
+ struct c2c_fmt *c2c_fmt;
+
+ c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
+ free(c2c_fmt);
+}
+
+bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
+{
+ struct c2c_fmt *c2c_a = container_of(a, struct c2c_fmt, fmt);
+ struct c2c_fmt *c2c_b = container_of(b, struct c2c_fmt, fmt);
+
+ return c2c_a->dim == c2c_b->dim;
+}
diff --git a/tools/perf/util/c2c.h b/tools/perf/util/c2c.h
new file mode 100644
index 000000000000..bd0c9d1c9a1a
--- /dev/null
+++ b/tools/perf/util/c2c.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PERF_UTIL_C2C_H
+#define __PERF_UTIL_C2C_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <linux/types.h>
+#include "hist.h"
+#include "mem-events.h"
+#include "stat.h"
+
+struct sort_entry;
+
+struct c2c_hists {
+ struct hists hists;
+ struct perf_hpp_list list;
+ struct c2c_stats stats;
+};
+
+struct compute_stats {
+ struct stats lcl_hitm;
+ struct stats rmt_hitm;
+ struct stats lcl_peer;
+ struct stats rmt_peer;
+ struct stats load;
+};
+
+struct c2c_hist_entry {
+ struct c2c_hists *hists;
+ struct evsel *evsel;
+ struct c2c_stats stats;
+ unsigned long *cpuset;
+ unsigned long *nodeset;
+ struct c2c_stats *node_stats;
+ unsigned int cacheline_idx;
+
+ struct compute_stats cstats;
+
+ unsigned long paddr;
+ unsigned long paddr_cnt;
+ bool paddr_zero;
+ char *nodestr;
+
+ /*
+ * must be at the end,
+ * because of its callchain dynamic entry
+ */
+ struct hist_entry he;
+};
+
+#define C2C_HEADER_MAX 2
+
+struct c2c_header {
+ struct {
+ const char *text;
+ int span;
+ } line[C2C_HEADER_MAX];
+};
+
+struct c2c_dimension {
+ struct c2c_header header;
+ const char *name;
+ int width;
+ struct sort_entry *se;
+
+ int64_t (*cmp)(struct perf_hpp_fmt *fmt,
+ struct hist_entry *left, struct hist_entry *right);
+ int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he);
+ int (*color)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he);
+};
+
+struct c2c_fmt {
+ struct perf_hpp_fmt fmt;
+ struct c2c_dimension *dim;
+};
+
+#define SYMBOL_WIDTH 30
+
+#define HEADER_LOW(__h) \
+ { \
+ .line[1] = { \
+ .text = __h, \
+ }, \
+ }
+
+#define HEADER_BOTH(__h0, __h1) \
+ { \
+ .line[0] = { \
+ .text = __h0, \
+ }, \
+ .line[1] = { \
+ .text = __h1, \
+ }, \
+ }
+
+void c2c_fmt_free(struct perf_hpp_fmt *fmt);
+bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b);
+
+#endif /* __PERF_UTIL_C2C_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v9 2/9] perf c2c: add function view model skeleton
2026-08-17 9:46 [PATCH v9 0/9] perf c2c: add a function view Jiebin Sun
2026-08-17 9:46 ` [PATCH v9 1/9] perf c2c: extract shared data structures into util/c2c.h Jiebin Sun
@ 2026-08-17 9:46 ` Jiebin Sun
2026-08-17 9:48 ` sashiko-bot
2026-08-17 20:53 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 3/9] perf c2c: add column rendering for function view Jiebin Sun
` (6 subsequent siblings)
8 siblings, 2 replies; 30+ messages in thread
From: Jiebin Sun @ 2026-08-17 9:46 UTC (permalink / raw)
To: namhyung
Cc: acme, mingo, peterz, adrian.hunter, alexander.shishkin, irogers,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, jiebin.sun, linux-perf-users,
linux-kernel
Add the initial common model for the c2c function view: model state and
small helpers shared by the hierarchy construction and formatting added
in later patches.
Build the model from util/ so it remains independent of the TUI and
command-private symbols.
Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
---
tools/perf/util/Build | 1 +
tools/perf/util/c2c-function.c | 66 ++++++++++++++++++++++++++++++++++
2 files changed, 67 insertions(+)
create mode 100644 tools/perf/util/c2c-function.c
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 1dfd92cbe3b7..b26a0b1ddfa3 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -12,6 +12,7 @@ perf-util-y += block-info.o
perf-util-y += block-range.o
perf-util-y += build-id.o
perf-util-y += c2c.o
+perf-util-y += c2c-function.o
perf-util-y += cacheline.o
perf-util-$(CONFIG_LIBCAPSTONE) += capstone.o
perf-util-y += config.o
diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
new file mode 100644
index 000000000000..ca82425a28dc
--- /dev/null
+++ b/tools/perf/util/c2c-function.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * C2C function model - function-level cacheline sharing analysis
+ *
+ * Displays a 3-level hierarchy showing which functions share cachelines:
+ * Level 1: Read-side functions sorted by Cycles % (estimated load cycles)
+ * Level 2: Functions sampled writing the shared lines read by level 1
+ * Level 3: The specific cachelines where the two functions contend
+ *
+ * Builds the hierarchy from the existing cacheline histograms
+ * (c2c_hist_entry->hists), reusing the shared c2c data structures.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tools/libc_compat.h> /* reallocarray */
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <linux/zalloc.h>
+
+#include "addr_location.h"
+#include "c2c.h"
+#include "cacheline.h"
+#include "hist.h"
+#include "map.h"
+#include "mem-events.h"
+#include "mem-info.h"
+#include "sort.h"
+#include "symbol.h"
+#include "thread.h"
+
+struct c2c_function_model {
+ struct c2c_hists function_hists;
+ /* Total estimated cycles across all level-1 entries. */
+ u64 total_cycles;
+ /* Source cacheline histograms; not owned here. */
+ struct c2c_hists *cl_hists;
+ /* --coalesce field list, used to require iaddr. */
+ const char *cl_sort;
+ /* Do not cap long symbol names. */
+ bool symbol_full;
+};
+
+static struct c2c_function_model c2c_ext __maybe_unused;
+
+static inline __maybe_unused u64 c2c_hitm_count(const struct c2c_stats *stats)
+{
+ return stats->tot_hitm;
+}
+
+static inline __maybe_unused bool symbol_name_equal(struct symbol *a, struct symbol *b)
+{
+ /* Two unknown symbols compare equal, matching cmp_null() in util/sort.c. */
+ if (!a || !b)
+ return a == b;
+ return arch__compare_symbol_names(a->name, b->name) == 0;
+}
+
+static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
+{
+ if (he->mem_info)
+ return mem_info__iaddr(he->mem_info)->addr;
+ return he->ip;
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v9 3/9] perf c2c: add column rendering for function view
2026-08-17 9:46 [PATCH v9 0/9] perf c2c: add a function view Jiebin Sun
2026-08-17 9:46 ` [PATCH v9 1/9] perf c2c: extract shared data structures into util/c2c.h Jiebin Sun
2026-08-17 9:46 ` [PATCH v9 2/9] perf c2c: add function view model skeleton Jiebin Sun
@ 2026-08-17 9:46 ` Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
2026-08-17 20:58 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 4/9] perf c2c: add HPP list parsing for function view columns Jiebin Sun
` (5 subsequent siblings)
8 siblings, 2 replies; 30+ messages in thread
From: Jiebin Sun @ 2026-08-17 9:46 UTC (permalink / raw)
To: namhyung
Cc: acme, mingo, peterz, adrian.hunter, alexander.shishkin, irogers,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, jiebin.sun, linux-perf-users,
linux-kernel
Add renderers for the function view's Cycles %, Store count, and
hierarchy identity columns. The identity column renders the read-side
function, contending writer, or cacheline, with indentation for the
hierarchy level. Also add width and header helpers, estimated-cycle
calculation, comparators, and the dimension table that ties them together.
Clamp the identity renderer's returned length to its local buffer before
using it for pointer and padding calculations. This handles snprintf-style
would-have-been lengths without changing normal output.
The next patch connects these dimensions to the view's HPP lists, so the
symbols used only there are temporarily marked __maybe_unused.
Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
---
tools/perf/util/c2c-function.c | 353 +++++++++++++++++++++++++++++++++
1 file changed, 353 insertions(+)
diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
index ca82425a28dc..7fce415c0f07 100644
--- a/tools/perf/util/c2c-function.c
+++ b/tools/perf/util/c2c-function.c
@@ -64,3 +64,356 @@ static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
return mem_info__iaddr(he->mem_info)->addr;
return he->ip;
}
+
+/*
+ * Hierarchy levels (by depth): L1 = read-side function, L2 = the writing
+ * function it contends with, L3 = the specific shared cacheline.
+ */
+static inline bool hist_entry__is_cacheline(struct hist_entry *he)
+{
+ return he->parent_he && he->parent_he->parent_he; /* level 3: cacheline */
+}
+
+/* Spaces of indent per hierarchy level, like the normal report view. */
+#define C2C_FUNC_INDENT 2
+
+/* Width of the folded-sign prefix ("%c ") each identity cell emits. */
+#define C2C_FUNC_FOLD_WIDTH 2
+
+/*
+ * Write he->depth levels of leading indentation into @buf, so lower-level
+ * entries are visually nested under their parent. Returns bytes written.
+ */
+static int hist_entry__indent(struct hist_entry *he, char *buf, size_t size)
+{
+ int indent = he->depth * C2C_FUNC_INDENT;
+
+ if (indent <= 0 || (size_t)indent >= size)
+ return 0;
+
+ return scnprintf(buf, size, "%*s", indent, "");
+}
+
+static int symbol_width(struct hists *hists, struct sort_entry *se)
+{
+ int width = hists__col_len(hists, se->se_width_idx);
+
+ /*
+ * Cap long symbol names as the cacheline view does. The stored column
+ * length is grown up front to fit the deepest, longest identity cell
+ * (including a level-3 cacheline address), so this cap never shrinks the
+ * column below what the cacheline address needs.
+ */
+ if (!c2c_ext.symbol_full && width > SYMBOL_WIDTH)
+ width = SYMBOL_WIDTH;
+
+ return width;
+}
+
+static struct c2c_dimension dim_symbol_view;
+
+/*
+ * c2c_width - Calculate width for a C2C column in function view
+ */
+static int c2c_width(struct perf_hpp_fmt *fmt,
+ struct perf_hpp *hpp __maybe_unused,
+ struct hists *hists)
+{
+ struct c2c_fmt *c2c_fmt;
+ struct c2c_dimension *dim;
+
+ c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
+ dim = c2c_fmt->dim;
+
+ if (dim == &dim_symbol_view)
+ return symbol_width(hists, dim->se);
+
+ return dim->se ? hists__col_len(hists, dim->se->se_width_idx) :
+ dim->width;
+}
+
+static int __maybe_unused c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hists *hists, int line, int *span)
+{
+ struct c2c_fmt *c2c_fmt;
+ struct c2c_dimension *dim;
+ const char *text = NULL;
+ int width = c2c_width(fmt, hpp, hists);
+
+ c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
+ dim = c2c_fmt->dim;
+
+ if (dim->se) {
+ text = dim->header.line[line].text;
+ /* Use the last line from sort_entry if not defined. */
+ if (!text && line == hists->hpp_list->nr_header_lines - 1)
+ text = dim->se->se_header;
+ } else {
+ text = dim->header.line[line].text;
+
+ if (span) {
+ if (*span) {
+ (*span)--;
+ return 0;
+ }
+
+ *span = dim->header.line[line].span;
+ }
+ }
+
+ if (!text)
+ text = "";
+
+ return scnprintf(hpp->buf, hpp->size, "%*s", width, text);
+}
+
+/*
+ * Return the estimated total cycles for a c2c_hist_entry
+ * (rmt_hitm + lcl_hitm + rmt_peer + lcl_peer + other loads).
+ */
+static u64 c2c_hist_entry__cycles(struct c2c_hist_entry *c2c_he)
+{
+ struct compute_stats *cs = &c2c_he->cstats;
+ double cycles = 0;
+
+ /*
+ * compute_stats() in builtin-c2c.c routes each load sample into exactly
+ * one cstats bucket (rmt_hitm, lcl_hitm, rmt_peer, lcl_peer or plain
+ * load), so each bucket's cycle total is its mean times its own sample
+ * count. Summing the per-bucket totals avoids both dropping peer-snoop
+ * cycles and double counting a sample that carries several data-source
+ * flags (e.g. Arm SPE sets HITM and PEER on the same load), which would
+ * happen if the mean were multiplied by the non-exclusive stats counts.
+ */
+ cycles += avg_stats(&cs->rmt_hitm) * cs->rmt_hitm.n;
+ cycles += avg_stats(&cs->lcl_hitm) * cs->lcl_hitm.n;
+ cycles += avg_stats(&cs->rmt_peer) * cs->rmt_peer.n;
+ cycles += avg_stats(&cs->lcl_peer) * cs->lcl_peer.n;
+ cycles += avg_stats(&cs->load) * cs->load.n;
+
+ return (u64)cycles;
+}
+
+/* Sum c2c_hist_entry__cycles() across all level-1 entries. */
+static u64 __maybe_unused c2c_ext__total_cycles(void)
+{
+ struct rb_node *nd;
+ u64 total = 0;
+
+ for (nd = rb_first_cached(&c2c_ext.function_hists.hists.entries); nd;
+ nd = rb_next(nd)) {
+ struct c2c_hist_entry *c2c_he =
+ rb_entry(nd, struct c2c_hist_entry, he.rb_node);
+
+ total += c2c_hist_entry__cycles(c2c_he);
+ }
+ return total;
+}
+
+/*
+ * Store count shown in the column: a level-3 cacheline leaf shows its parent
+ * level-2 writer's stores on that line, not all stores on the line. A level-2
+ * writer shows the sum across its level-3 cachelines. A level-1 reader shows
+ * the sum across all included writers on the cachelines it reads; this is not
+ * the reader function's own store count and is not additive across readers.
+ */
+static u64 hist_entry__displayed_stores(struct hist_entry *he)
+{
+ struct c2c_hist_entry *c2c_he = container_of(he, struct c2c_hist_entry, he);
+ struct rb_node *nd;
+ u64 stores = 0;
+
+ /* Level-2/3 entries already aggregate the stores they represent. */
+ if (he->parent_he)
+ return c2c_he->stats.store;
+
+ for (nd = rb_first_cached(&he->hroot_out); nd; nd = rb_next(nd)) {
+ struct c2c_hist_entry *child_c2c =
+ rb_entry(nd, struct c2c_hist_entry, he.rb_node);
+ stores += child_c2c->stats.store;
+ }
+ return stores;
+}
+
+static int
+total_stores_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he)
+{
+ int width = c2c_width(fmt, hpp, he->hists);
+ u64 total = hist_entry__displayed_stores(he);
+
+ return scnprintf(hpp->buf, hpp->size, "%*" PRIu64, width, total);
+}
+
+/*
+ * symbol_view_entry - Render the unified, indented identity column.
+ *
+ * All three levels share this single column so the hierarchy reads top-down
+ * with progressive indentation, like the normal report hierarchy view. It is
+ * a function-centric view with no dedicated code-address column. Verbose
+ * function rows can still include a representative address:
+ * L1 read-side function: "- [k] cpupri_set"
+ * L2 writing function: " - [k] pull_rt_task"
+ * L3 shared cacheline: " 0xff2d0082809da080"
+ */
+static int
+symbol_view_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he)
+{
+ int width = c2c_width(fmt, hpp, he->hists);
+ int text_width;
+ int ret;
+ char folded_sign;
+
+ ret = hist_entry__indent(he, hpp->buf, hpp->size);
+
+ folded_sign = he->has_children ? (he->unfolded ? '-' : '+') : ' ';
+ ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%c ", folded_sign);
+
+ text_width = width - ret;
+ if (text_width <= 0)
+ return ret;
+
+ if (hist_entry__is_cacheline(he)) {
+ /* Level 3: the shared cacheline address. */
+ u64 addr = he->mem_info ?
+ cl_address(mem_info__daddr(he->mem_info)->addr, chk_double_cl) : 0;
+ char symbuf[32];
+
+ scnprintf(symbuf, sizeof(symbuf), "0x%" PRIx64, addr);
+ ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%-*.*s",
+ text_width, text_width, symbuf);
+ } else {
+ /* Level 1 and level 2 are both functions. */
+ size_t cell_size;
+ int len;
+
+ if ((size_t)ret >= hpp->size)
+ return ret;
+
+ cell_size = min_t(size_t, hpp->size - ret,
+ (size_t)text_width + 1);
+ len = sort_sym.se_snprintf(he, hpp->buf + ret, cell_size,
+ text_width);
+ /*
+ * se_snprintf() accumulates repsep_snprintf() calls, which cap
+ * their return at the remaining size - 1 rather than reporting
+ * what the format would have needed, so len stays below
+ * cell_size. Clamp anyway so ret cannot leave hpp->buf.
+ */
+ if (len < 0)
+ len = 0;
+ else
+ len = min_t(size_t, len, cell_size - 1);
+
+ ret += len;
+ if (len < text_width)
+ ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%*s",
+ text_width - len, "");
+ }
+
+ return ret;
+}
+
+/*
+ * cycles_percent_entry - Render cycles percentage column
+ */
+static int
+cycles_percent_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he)
+{
+ struct c2c_hist_entry *c2c_he;
+ int width = c2c_width(fmt, hpp, he->hists);
+ u64 fn_cycles, total_cycles;
+ char folded_sign;
+ double pct;
+ int ret, pct_width;
+
+ /* Hide Cycles Percent for child functions and cachelines. */
+ if (he->parent_he)
+ return scnprintf(hpp->buf, hpp->size, "%*s", width, "");
+
+ c2c_he = container_of(he, struct c2c_hist_entry, he);
+ fn_cycles = c2c_hist_entry__cycles(c2c_he);
+ /* Populated by build_function_view_hierarchy() once the L1 tree is built. */
+ total_cycles = c2c_ext.total_cycles;
+ pct = total_cycles > 0 ? (double)fn_cycles / total_cycles * 100.0 : 0.0;
+
+ /* Add folded sign only for level-1 entries */
+ folded_sign = he->has_children ? (he->unfolded ? '-' : '+') : ' ';
+ ret = scnprintf(hpp->buf, hpp->size, "%c ", folded_sign);
+
+ pct_width = width - ret;
+ if (pct_width <= 0)
+ return ret;
+ ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%*.2f%%", pct_width - 1, pct);
+ return ret;
+}
+
+/*
+ * cycles_percent_cmp - Comparison function for cycles percentage sorting
+ */
+static int64_t
+cycles_percent_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
+ struct hist_entry *left, struct hist_entry *right)
+{
+ struct c2c_hist_entry *c2c_left = container_of(left, struct c2c_hist_entry, he);
+ struct c2c_hist_entry *c2c_right = container_of(right, struct c2c_hist_entry, he);
+ u64 cycles_left, cycles_right;
+
+ /* Cycles Percent is only shown for level-1 entries; others compare equal. */
+ if (left->parent_he || right->parent_he)
+ return 0;
+
+ cycles_left = c2c_hist_entry__cycles(c2c_left);
+ cycles_right = c2c_hist_entry__cycles(c2c_right);
+
+ return (cycles_left > cycles_right) - (cycles_left < cycles_right);
+}
+
+/*
+ * total_stores_cmp - Comparison function for total stores sorting
+ */
+static int64_t
+total_stores_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
+ struct hist_entry *left, struct hist_entry *right)
+{
+ u64 left_store = hist_entry__displayed_stores(left);
+ u64 right_store = hist_entry__displayed_stores(right);
+
+ return (left_store > right_store) - (left_store < right_store);
+}
+
+/*
+ * Function view dimensions
+ */
+static struct c2c_dimension dim_cycles_percent = {
+ .header = HEADER_BOTH("Cycles", "%"),
+ .name = "cycles_percent",
+ .cmp = cycles_percent_cmp,
+ .entry = cycles_percent_entry,
+ .width = 9,
+};
+
+static struct c2c_dimension dim_total_stores = {
+ .header = HEADER_BOTH("Store", "count"),
+ .name = "total_stores",
+ .cmp = total_stores_cmp,
+ .entry = total_stores_entry,
+ .width = 7,
+};
+
+static struct c2c_dimension dim_symbol_view = {
+ .header = HEADER_LOW("Function / Contending function / Cacheline"),
+ .name = "symbol_view",
+ .se = &sort_sym,
+ .entry = symbol_view_entry,
+ .width = SYMBOL_WIDTH,
+};
+
+static struct c2c_dimension *function_view_dimensions[] __maybe_unused = {
+ &dim_cycles_percent,
+ &dim_total_stores,
+ &dim_symbol_view,
+ NULL,
+};
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v9 4/9] perf c2c: add HPP list parsing for function view columns
2026-08-17 9:46 [PATCH v9 0/9] perf c2c: add a function view Jiebin Sun
` (2 preceding siblings ...)
2026-08-17 9:46 ` [PATCH v9 3/9] perf c2c: add column rendering for function view Jiebin Sun
@ 2026-08-17 9:46 ` Jiebin Sun
2026-08-17 9:50 ` sashiko-bot
2026-08-17 21:04 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 5/9] perf c2c: add function view stats merge and memory management Jiebin Sun
` (4 subsequent siblings)
8 siblings, 2 replies; 30+ messages in thread
From: Jiebin Sun @ 2026-08-17 9:46 UTC (permalink / raw)
To: namhyung
Cc: acme, mingo, peterz, adrian.hunter, alexander.shishkin, irogers,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, jiebin.sun, linux-perf-users,
linux-kernel
Add the parser that builds the function view's local HPP output and
sort lists from field strings. This includes dimension lookup, comparator
wrappers, c2c_fmt allocation, and the initialization entry points used by
the hierarchy builder.
The generic perf_hpp__setup_output_field() registers formats on the global
perf_hpp_list. Using it here would leave the function view's local list
without output columns and modify the cacheline view's list instead. Add
c2c_function_hists__setup_output_field() to append sort keys to the local
output list.
Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
---
tools/perf/util/c2c-function.c | 269 ++++++++++++++++++++++++++++++++-
1 file changed, 266 insertions(+), 3 deletions(-)
diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
index 7fce415c0f07..d27544016660 100644
--- a/tools/perf/util/c2c-function.c
+++ b/tools/perf/util/c2c-function.c
@@ -23,6 +23,7 @@
#include "addr_location.h"
#include "c2c.h"
#include "cacheline.h"
+#include "debug.h"
#include "hist.h"
#include "map.h"
#include "mem-events.h"
@@ -132,8 +133,8 @@ static int c2c_width(struct perf_hpp_fmt *fmt,
dim->width;
}
-static int __maybe_unused c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
- struct hists *hists, int line, int *span)
+static int c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hists *hists, int line, int *span)
{
struct c2c_fmt *c2c_fmt;
struct c2c_dimension *dim;
@@ -411,9 +412,271 @@ static struct c2c_dimension dim_symbol_view = {
.width = SYMBOL_WIDTH,
};
-static struct c2c_dimension *function_view_dimensions[] __maybe_unused = {
+static struct c2c_dimension *function_view_dimensions[] = {
&dim_cycles_percent,
&dim_total_stores,
&dim_symbol_view,
NULL,
};
+
+static struct c2c_dimension *get_function_dimension(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; function_view_dimensions[i]; i++) {
+ struct c2c_dimension *dim = function_view_dimensions[i];
+
+ if (!strcmp(dim->name, name))
+ return dim;
+ }
+
+ return NULL;
+}
+
+/* Wrappers so sort_entry-backed dimensions sort/collapse via their se. */
+static int64_t c2c_se_cmp(struct perf_hpp_fmt *fmt,
+ struct hist_entry *a, struct hist_entry *b)
+{
+ struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
+ struct c2c_dimension *dim = c2c_fmt->dim;
+
+ return dim->se->se_cmp(a, b);
+}
+
+static int64_t c2c_se_collapse(struct perf_hpp_fmt *fmt,
+ struct hist_entry *a, struct hist_entry *b)
+{
+ struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
+ struct c2c_dimension *dim = c2c_fmt->dim;
+ int64_t (*collapse_fn)(struct hist_entry *a, struct hist_entry *b);
+
+ collapse_fn = dim->se->se_collapse ?: dim->se->se_cmp;
+ return collapse_fn(a, b);
+}
+
+static int64_t c2c_se_sort(struct perf_hpp_fmt *fmt,
+ struct hist_entry *a, struct hist_entry *b)
+{
+ struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
+ struct c2c_dimension *dim = c2c_fmt->dim;
+ int64_t (*sort_fn)(struct hist_entry *a, struct hist_entry *b);
+
+ sort_fn = dim->se->se_sort ?: dim->se->se_cmp;
+ return sort_fn(a, b);
+}
+
+/*
+ * Build the c2c_fmt for @name. Returns:
+ * 0 and *fmtp set on success;
+ * -ENOENT and *fmtp = NULL if @name is not a function-view dimension;
+ * -ENOMEM if allocation failed (distinct from -ENOENT so
+ * the caller does not misreport it as an
+ * "invalid field").
+ */
+static int get_function_format(const char *name, struct c2c_fmt **fmtp)
+{
+ struct c2c_dimension *dim = get_function_dimension(name);
+ struct c2c_fmt *c2c_fmt;
+ struct perf_hpp_fmt *fmt;
+
+ *fmtp = NULL;
+
+ if (!dim)
+ return -ENOENT;
+
+ c2c_fmt = zalloc(sizeof(*c2c_fmt));
+ if (!c2c_fmt)
+ return -ENOMEM;
+
+ fmt = &c2c_fmt->fmt;
+
+ c2c_fmt->dim = dim;
+ INIT_LIST_HEAD(&fmt->list);
+ INIT_LIST_HEAD(&fmt->sort_list);
+
+ fmt->cmp = dim->se ? c2c_se_cmp : dim->cmp;
+ fmt->sort = dim->se ? c2c_se_sort : dim->cmp;
+ fmt->color = dim->color;
+ fmt->entry = dim->entry;
+ fmt->header = c2c_header;
+ fmt->width = c2c_width;
+ fmt->collapse = dim->se ? c2c_se_collapse : dim->cmp;
+ fmt->equal = c2c_fmt_equal;
+ fmt->free = c2c_fmt_free;
+
+ *fmtp = c2c_fmt;
+ return 0;
+}
+
+static int
+c2c_function_hists__init_output(struct perf_hpp_list *hpp_list, char *name,
+ struct perf_env *env __maybe_unused)
+{
+ struct c2c_fmt *c2c_fmt;
+ int ret;
+
+ ret = get_function_format(name, &c2c_fmt);
+ if (ret == -ENOMEM)
+ return ret;
+ /* The function view only accepts its own dimensions. */
+ if (ret == -ENOENT)
+ return -EINVAL;
+
+ /*
+ * Mark symbol-backed columns so hists__has(hists, sym) is correct.
+ * Only dim_symbol_view carries a sort_entry (.se); the function
+ * view's field strings are fixed and always include symbol_view, so
+ * this single check is sufficient (unlike the user-configurable
+ * cacheline view, which must also test dim_iaddr).
+ */
+ if (c2c_fmt->dim->se == &sort_sym)
+ hpp_list->sym = 1;
+
+ perf_hpp_list__column_register(hpp_list, &c2c_fmt->fmt);
+ return 0;
+}
+
+static int
+c2c_function_hists__init_sort(struct perf_hpp_list *hpp_list, char *name,
+ struct perf_env *env __maybe_unused)
+{
+ struct c2c_fmt *c2c_fmt;
+ int ret;
+
+ ret = get_function_format(name, &c2c_fmt);
+ if (ret == -ENOMEM)
+ return ret;
+ /* The function view only accepts its own dimensions. */
+ if (ret == -ENOENT)
+ return -EINVAL;
+
+ /* Mark symbol-backed sort keys so hists__has(hists, sym) is correct. */
+ if (c2c_fmt->dim->se == &sort_sym)
+ hpp_list->sym = 1;
+
+ perf_hpp_list__register_sort_field(hpp_list, &c2c_fmt->fmt);
+ return 0;
+}
+
+typedef int (*hpp_list_add_fn)(struct perf_hpp_list *hpp_list, char *name,
+ struct perf_env *env);
+
+static int function_hpp_list__add_tokens(struct perf_hpp_list *hpp_list, char *list,
+ struct perf_env *env, hpp_list_add_fn add)
+{
+ char *tok, *tmp;
+ int ret;
+
+ if (!list)
+ return 0;
+
+ for (tok = strtok_r(list, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) {
+ ret = add(hpp_list, tok, env);
+ if (ret) {
+ if (ret == -EINVAL || ret == -ESRCH)
+ pr_err("Invalid c2c function-view field: %s\n", tok);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Append the function view's sort keys to its own output fields, mirroring
+ * perf_hpp__setup_output_field() but on the local @list. The shared helper
+ * registers onto the global perf_hpp_list, which would leave this local list
+ * without output columns, so the function view keeps its own copy here.
+ */
+static void c2c_function_hists__setup_output_field(struct perf_hpp_list *list)
+{
+ struct perf_hpp_fmt *fmt;
+
+ perf_hpp_list__for_each_sort_list(list, fmt) {
+ struct perf_hpp_fmt *pos;
+
+ if (!fmt->entry && !fmt->color)
+ continue;
+
+ perf_hpp_list__for_each_format(list, pos) {
+ if (c2c_fmt_equal(fmt, pos))
+ goto next;
+ }
+
+ perf_hpp_list__column_register(list, fmt);
+next:
+ continue;
+ }
+}
+
+static int
+function_hpp_list__parse(struct perf_hpp_list *hpp_list,
+ const char *output_str,
+ const char *sort_str,
+ struct perf_env *env)
+{
+ char *output = output_str ? strdup(output_str) : NULL;
+ char *sort = sort_str ? strdup(sort_str) : NULL;
+ int ret = 0;
+
+ if ((output_str && !output) || (sort_str && !sort)) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = function_hpp_list__add_tokens(hpp_list, output, env,
+ c2c_function_hists__init_output);
+ if (ret)
+ goto out;
+
+ ret = function_hpp_list__add_tokens(hpp_list, sort, env,
+ c2c_function_hists__init_sort);
+ if (ret)
+ goto out;
+
+ c2c_function_hists__setup_output_field(hpp_list);
+out:
+ if (ret)
+ perf_hpp__reset_output_field(hpp_list);
+ free(output);
+ free(sort);
+ return ret;
+}
+
+static int __maybe_unused
+c2c_function_hists__init(struct c2c_hists *hists,
+ const char *sort,
+ int nr_header_lines,
+ struct perf_env *env)
+{
+ __hists__init(&hists->hists, &hists->list);
+
+ perf_hpp_list__init(&hists->list);
+
+ hists->list.nr_header_lines = nr_header_lines;
+
+ return function_hpp_list__parse(&hists->list, /*output=*/NULL, sort, env);
+}
+
+static int __maybe_unused
+c2c_function_hists__reinit(struct c2c_hists *c2c_hists,
+ const char *output,
+ const char *sort,
+ struct perf_env *env)
+{
+ int nr_header_lines = c2c_hists->list.nr_header_lines;
+
+ perf_hpp__reset_output_field(&c2c_hists->list);
+
+ /* Clear stale state flags so a different output/sort set starts fresh. */
+ c2c_hists->list.need_collapse = 0;
+ c2c_hists->list.parent = 0;
+ c2c_hists->list.sym = 0;
+ c2c_hists->list.dso = 0;
+ c2c_hists->list.socket = 0;
+ c2c_hists->list.thread = 0;
+ c2c_hists->list.comm = 0;
+ c2c_hists->list.comm_nodigit = 0;
+ c2c_hists->list.nr_header_lines = nr_header_lines;
+
+ return function_hpp_list__parse(&c2c_hists->list, output, sort, env);
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v9 5/9] perf c2c: add function view stats merge and memory management
2026-08-17 9:46 [PATCH v9 0/9] perf c2c: add a function view Jiebin Sun
` (3 preceding siblings ...)
2026-08-17 9:46 ` [PATCH v9 4/9] perf c2c: add HPP list parsing for function view columns Jiebin Sun
@ 2026-08-17 9:46 ` Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
2026-08-17 21:05 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 6/9] perf c2c: add function view hierarchy entry creation Jiebin Sun
` (3 subsequent siblings)
8 siblings, 2 replies; 30+ messages in thread
From: Jiebin Sun @ 2026-08-17 9:46 UTC (permalink / raw)
To: namhyung
Cc: acme, mingo, peterz, adrian.hunter, alexander.shishkin, irogers,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, jiebin.sun, linux-perf-users,
linux-kernel
Add the per-entry stats/cstats aggregation helpers and hierarchy teardown.
Child common fields are released through hist_entry__delete(), while the
function-view free callback handles the private child tree and containing
allocation. Also add a helper for pruning writer entries with no stores or
cacheline children.
These are used by the entry-creation and builder patches that follow and
are __maybe_unused until then.
Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
---
tools/perf/util/c2c-function.c | 163 +++++++++++++++++++++++++++++++++
1 file changed, 163 insertions(+)
diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
index d27544016660..aa6be7c72977 100644
--- a/tools/perf/util/c2c-function.c
+++ b/tools/perf/util/c2c-function.c
@@ -680,3 +680,166 @@ c2c_function_hists__reinit(struct c2c_hists *c2c_hists,
return function_hpp_list__parse(&c2c_hists->list, output, sort, env);
}
+
+/* Welford online merge of two "stats" (from util/stat.h) accumulators. */
+static void c2c_stats_merge(struct stats *dest, const struct stats *src)
+{
+ double delta;
+
+ if (src->n == 0)
+ return;
+
+ if (dest->n == 0) {
+ *dest = *src;
+ return;
+ }
+
+ delta = src->mean - dest->mean;
+ dest->M2 += src->M2 + delta * delta * dest->n * src->n / (dest->n + src->n);
+ dest->mean = (dest->mean * dest->n + src->mean * src->n) / (dest->n + src->n);
+ dest->n += src->n;
+
+ /* Update min/max */
+ if (src->max > dest->max)
+ dest->max = src->max;
+ if (src->min < dest->min)
+ dest->min = src->min;
+}
+
+/* Merge compute_stats during function aggregation. */
+static void __maybe_unused c2c_add_cstats(struct compute_stats *dest,
+ const struct compute_stats *src)
+{
+ c2c_stats_merge(&dest->rmt_hitm, &src->rmt_hitm);
+ c2c_stats_merge(&dest->lcl_hitm, &src->lcl_hitm);
+ c2c_stats_merge(&dest->rmt_peer, &src->rmt_peer);
+ c2c_stats_merge(&dest->lcl_peer, &src->lcl_peer);
+ c2c_stats_merge(&dest->load, &src->load);
+}
+
+static bool __maybe_unused hist_entry__add_c2c_stats(struct hist_entry *he,
+ const struct c2c_stats *stats)
+{
+ u64 nr_events = c2c_hitm_count(stats) + stats->rmt_peer + stats->lcl_peer;
+ u64 weight1 = c2c_hitm_count(stats);
+
+ /*
+ * Allocate before touching he->stat, so a failure here leaves the
+ * entry unmodified and the caller can bail out without having
+ * half-updated the statistics.
+ */
+ if (symbol_conf.cumulate_callchain && !he->stat_acc) {
+ he->stat_acc = calloc(1, sizeof(struct he_stat));
+ if (!he->stat_acc)
+ return false;
+ }
+
+ he->stat.nr_events += nr_events;
+ he->stat.period += nr_events;
+ he->stat.weight1 += weight1;
+
+ if (!symbol_conf.cumulate_callchain)
+ return true;
+
+ he->stat_acc->nr_events += nr_events;
+ he->stat_acc->period += nr_events;
+ he->stat_acc->weight1 += weight1;
+
+ return true;
+}
+
+static void c2c_he__free_hierarchy(struct hist_entry *he);
+
+/*
+ * Free a function-view histogram entry (hist_entry_ops::free).
+ */
+static void __maybe_unused c2c_function_he_free(void *ptr)
+{
+ struct hist_entry *he = ptr;
+ struct c2c_hist_entry *c2c_he;
+
+ c2c_he = container_of(he, struct c2c_hist_entry, he);
+
+ if (c2c_he->hists) {
+ perf_hpp__reset_output_field(&c2c_he->hists->list);
+ hists__delete_all_entries(&c2c_he->hists->hists);
+ zfree(&c2c_he->hists);
+ }
+
+ c2c_he__free_hierarchy(he);
+
+ zfree(&c2c_he->nodeset);
+ zfree(&c2c_he->cpuset);
+ zfree(&c2c_he->nodestr);
+ zfree(&c2c_he->node_stats);
+
+ free(c2c_he);
+}
+
+static void c2c_he__free_hierarchy(struct hist_entry *he)
+{
+ struct rb_node *nd;
+ struct hist_entry *child_he;
+
+ /*
+ * A leaf entry stores its callchains in the sorted_chain member, which
+ * shares a union with the hroot_in/hroot_out child trees, so its
+ * hroot_out is not a valid subtree to walk. Leaf entries never have a
+ * child hierarchy here, so stop before touching hroot_out.
+ */
+ if (he->leaf)
+ return;
+
+ if (RB_EMPTY_ROOT(&he->hroot_out.rb_root))
+ return;
+
+ nd = rb_first_cached(&he->hroot_out);
+ while (nd) {
+ struct rb_node *next = rb_next(nd);
+
+ child_he = rb_entry(nd, struct hist_entry, rb_node);
+ rb_erase_cached(&child_he->rb_node, &he->hroot_out);
+ hist_entry__delete(child_he);
+
+ nd = next;
+ }
+
+ /* All children erased; clear the tree (and its cached leftmost). */
+ he->hroot_out = RB_ROOT_CACHED;
+}
+
+/*
+ * Drop level-2 writing functions that carry no stores or
+ * no cacheline children. Writers are only added when they store into a shared
+ * line, so this is mainly a safety net. Returns the number of surviving
+ * writers.
+ */
+static int __maybe_unused c2c_he__prune_empty_writers(struct hist_entry *l1_he)
+{
+ struct rb_node *nd;
+ int surviving = 0;
+
+ if (!l1_he->has_children)
+ return 0;
+
+ nd = rb_first_cached(&l1_he->hroot_out);
+ while (nd) {
+ struct rb_node *next = rb_next(nd);
+ struct hist_entry *l2_he = rb_entry(nd, struct hist_entry, rb_node);
+
+ if (l2_he->has_children && hist_entry__displayed_stores(l2_he) > 0) {
+ surviving++;
+ } else {
+ rb_erase_cached(&l2_he->rb_node, &l1_he->hroot_out);
+ hist_entry__delete(l2_he);
+ }
+ nd = next;
+ }
+
+ if (!surviving) {
+ l1_he->hroot_out = RB_ROOT_CACHED;
+ l1_he->has_children = false;
+ l1_he->unfolded = false;
+ }
+ return surviving;
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v9 6/9] perf c2c: add function view hierarchy entry creation
2026-08-17 9:46 [PATCH v9 0/9] perf c2c: add a function view Jiebin Sun
` (4 preceding siblings ...)
2026-08-17 9:46 ` [PATCH v9 5/9] perf c2c: add function view stats merge and memory management Jiebin Sun
@ 2026-08-17 9:46 ` Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
2026-08-17 21:06 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 7/9] perf c2c: build and finalize the function view hierarchy Jiebin Sun
` (2 subsequent siblings)
8 siblings, 2 replies; 30+ messages in thread
From: Jiebin Sun @ 2026-08-17 9:46 UTC (permalink / raw)
To: namhyung
Cc: acme, mingo, peterz, adrian.hunter, alexander.shishkin, irogers,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, jiebin.sun, linux-perf-users,
linux-kernel
Add the entry-creation layer: owned-reference child allocation and
insertion, and the level-1/2/3 lookup-or-create functions keyed by
function symbol (level 1 read-side, level 2 writer) and by the source
cacheline's existing index (level 3).
Give synthetic children normal entry operations and acquire their thread
and map-symbol references. This lets the hierarchy teardown use
hist_entry__delete() for the common fields while the function-view free
callback handles the private child tree and containing allocation.
Reuse cacheline_idx to preserve the source entry identity without adding
function-view-only state. Add c2c_function__find_cacheline() to locate the
original cacheline entry by the same index.
These are driven by the hierarchy builder in the next patch and are
__maybe_unused until then.
Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
---
tools/perf/util/c2c-function.c | 319 +++++++++++++++++++++++++++++++--
tools/perf/util/c2c.h | 2 +
2 files changed, 310 insertions(+), 11 deletions(-)
diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
index aa6be7c72977..c005fb5454a7 100644
--- a/tools/perf/util/c2c-function.c
+++ b/tools/perf/util/c2c-function.c
@@ -24,6 +24,7 @@
#include "c2c.h"
#include "cacheline.h"
#include "debug.h"
+#include "dso.h"
#include "hist.h"
#include "map.h"
#include "mem-events.h"
@@ -51,12 +52,34 @@ static inline __maybe_unused u64 c2c_hitm_count(const struct c2c_stats *stats)
return stats->tot_hitm;
}
-static inline __maybe_unused bool symbol_name_equal(struct symbol *a, struct symbol *b)
+static int64_t c2c_function_cmp(const struct map_symbol *left,
+ const struct map_symbol *right)
{
- /* Two unknown symbols compare equal, matching cmp_null() in util/sort.c. */
- if (!a || !b)
- return a == b;
- return arch__compare_symbol_names(a->name, b->name) == 0;
+ const struct dso *left_dso = left->map ? map__dso(left->map) : NULL;
+ const struct dso *right_dso = right->map ? map__dso(right->map) : NULL;
+ int ret;
+
+ if (!left_dso || !right_dso) {
+ if (left_dso != right_dso)
+ return left_dso ? 1 : -1;
+ } else {
+ /*
+ * Use the same DSO name as _sort__dso_cmp() (short name unless
+ * verbose), so this matches the DSO comparison the level-1
+ * entries are deduplicated by; otherwise same-basename DSOs
+ * could be split or merged inconsistently across levels.
+ */
+ const char *left_name = verbose > 0 ?
+ dso__long_name(left_dso) : dso__short_name(left_dso);
+ const char *right_name = verbose > 0 ?
+ dso__long_name(right_dso) : dso__short_name(right_dso);
+
+ ret = strcmp(left_name, right_name);
+ if (ret)
+ return ret;
+ }
+
+ return _sort__sym_cmp(left->sym, right->sym);
}
static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
@@ -753,7 +776,7 @@ static void c2c_he__free_hierarchy(struct hist_entry *he);
/*
* Free a function-view histogram entry (hist_entry_ops::free).
*/
-static void __maybe_unused c2c_function_he_free(void *ptr)
+static void c2c_function_he_free(void *ptr)
{
struct hist_entry *he = ptr;
struct c2c_hist_entry *c2c_he;
@@ -768,11 +791,6 @@ static void __maybe_unused c2c_function_he_free(void *ptr)
c2c_he__free_hierarchy(he);
- zfree(&c2c_he->nodeset);
- zfree(&c2c_he->cpuset);
- zfree(&c2c_he->nodestr);
- zfree(&c2c_he->node_stats);
-
free(c2c_he);
}
@@ -843,3 +861,282 @@ static int __maybe_unused c2c_he__prune_empty_writers(struct hist_entry *l1_he)
}
return surviving;
}
+
+static void *c2c_function_he_zalloc(size_t size)
+{
+ struct c2c_hist_entry *c2c_he = zalloc(sizeof(*c2c_he) + size);
+
+ if (!c2c_he)
+ return NULL;
+
+ init_stats(&c2c_he->cstats.lcl_hitm);
+ init_stats(&c2c_he->cstats.rmt_hitm);
+ init_stats(&c2c_he->cstats.lcl_peer);
+ init_stats(&c2c_he->cstats.rmt_peer);
+ init_stats(&c2c_he->cstats.load);
+
+ return &c2c_he->he;
+}
+
+/* Entry operations for function view */
+static struct hist_entry_ops c2c_function_entry_ops = {
+ .new = c2c_function_he_zalloc,
+ .free = c2c_function_he_free,
+};
+
+static struct c2c_hist_entry *
+c2c_child_entry__alloc(struct hist_entry *parent_he, struct hist_entry *src_he,
+ int depth, u64 ip)
+{
+ struct c2c_hist_entry *child_c2c;
+ struct hist_entry *child_he;
+
+ /* Function-view children never own or display callchains. */
+ child_he = c2c_function_he_zalloc(0);
+ if (!child_he)
+ return NULL;
+
+ child_c2c = container_of(child_he, struct c2c_hist_entry, he);
+ child_he->ops = &c2c_function_entry_ops;
+ map_symbol__copy(&child_he->ms, &src_he->ms);
+
+ if (src_he->mem_info) {
+ child_he->mem_info = mem_info__clone(src_he->mem_info);
+ if (!child_he->mem_info)
+ goto out_free;
+ }
+
+ child_he->thread = thread__get(src_he->thread);
+ child_he->cpumode = src_he->cpumode;
+ child_he->cpu = src_he->cpu;
+ child_he->socket = src_he->socket;
+ child_he->level = src_he->level;
+ child_he->ip = ip;
+
+ child_he->parent_he = parent_he;
+ child_he->depth = depth;
+ child_he->leaf = (depth >= 2);
+ child_he->hists = &c2c_ext.function_hists.hists;
+ child_he->filtered = false;
+ child_he->unfolded = false;
+ child_he->has_children = false;
+ child_he->has_no_entry = false;
+ child_he->nr_rows = 0;
+ child_he->row_offset = 0;
+
+ memset(&child_he->stat, 0, sizeof(child_he->stat));
+ child_he->hroot_in = RB_ROOT_CACHED;
+ child_he->hroot_out = RB_ROOT_CACHED;
+ INIT_LIST_HEAD(&child_he->pairs.node);
+ child_he->hpp_list = &c2c_ext.function_hists.list;
+ if (symbol_conf.cumulate_callchain) {
+ child_he->stat_acc = calloc(1, sizeof(struct he_stat));
+ if (!child_he->stat_acc)
+ goto out_free;
+ }
+
+ return child_c2c;
+
+out_free:
+ hist_entry__delete(child_he);
+ return NULL;
+}
+
+static void
+c2c_child_entry__insert(struct hist_entry *parent_he, struct hist_entry *child_he,
+ struct rb_node **p, struct rb_node *rb_parent, bool leftmost)
+{
+ rb_link_node(&child_he->rb_node, rb_parent, p);
+ rb_insert_color_cached(&child_he->rb_node, &parent_he->hroot_out, leftmost);
+
+ parent_he->has_children = true;
+ parent_he->leaf = false;
+}
+
+static __maybe_unused struct hist_entry *
+c2c_function_hists__level1_entry(struct symbol *sym,
+ struct hist_entry *detail_he,
+ struct thread *synthetic_thread)
+{
+ struct addr_location al;
+ struct perf_sample sample = {};
+ struct mem_info *mi;
+ struct hist_entry *he;
+ /*
+ * Key the level-1 entry by the function, not by a specific code
+ * address: use the symbol start so every instruction address inside
+ * the same function collapses into one entry. This makes level 1 a
+ * true "function view" rather than a per-code-address view.
+ */
+ u64 sym_start = (sym && detail_he->ms.map) ?
+ map__unmap_ip(detail_he->ms.map, sym->start) : detail_he->ip;
+
+ mi = mem_info__new();
+ if (!mi)
+ return NULL;
+
+ mem_info__iaddr(mi)->addr = sym_start;
+ /* mem_info__put() will map_symbol__exit() these, so take refs. */
+ mem_info__iaddr(mi)->ms.thread = thread__get(detail_he->ms.thread);
+ mem_info__iaddr(mi)->ms.map = map__get(detail_he->ms.map);
+ mem_info__iaddr(mi)->ms.sym = sym;
+ mem_info__daddr(mi)->addr = 0;
+
+ addr_location__init(&al);
+ al.thread = thread__get(synthetic_thread);
+ al.map = map__get(detail_he->ms.map);
+ al.sym = sym;
+ al.addr = sym_start;
+ al.level = detail_he->level;
+ al.cpumode = detail_he->cpumode;
+ al.cpu = 0;
+ al.socket = 0;
+ al.filtered = 0;
+ al.latency = 0;
+
+ /*
+ * Synthetic sample: period/weight are placeholders only. The real
+ * c2c counters live in c2c_hist_entry::stats and are added via
+ * hist_entry__add_c2c_stats(); no function-view column or sort key
+ * reads he->stat.period/nr_events, so the +1 that __hists__add_entry()
+ * accrues on each dedup hit has no effect on what is displayed.
+ */
+ sample.period = 1;
+ sample.weight = 1;
+ sample.ip = sym_start;
+ sample.pid = thread__pid(synthetic_thread);
+ sample.tid = thread__tid(synthetic_thread);
+ sample.cpu = 0;
+
+ /* Add entry - histogram handles dedup */
+ he = hists__add_entry_ops(&c2c_ext.function_hists.hists,
+ &c2c_function_entry_ops,
+ &al, NULL, NULL, mi,
+ NULL, &sample, true);
+
+ addr_location__exit(&al);
+ mem_info__put(mi);
+
+ if (he)
+ he->hpp_list = &c2c_ext.function_hists.list;
+
+ return he;
+}
+
+/*
+ * Level 2: a function that writes a cacheline the level-1 function reads,
+ * keyed by the DSO display name and symbol, consistently with perf's symbol
+ * sort semantics. All code addresses and cachelines for the same writer
+ * function aggregate into one row.
+ */
+static __maybe_unused struct c2c_hist_entry *
+c2c_function_hists__level2_entry(struct c2c_hist_entry *level1_c2c,
+ struct symbol *sym, struct hist_entry *detail_he)
+{
+ struct hist_entry *level1_he = &level1_c2c->he;
+ struct rb_node **p = &level1_he->hroot_out.rb_root.rb_node;
+ struct rb_node *parent = NULL;
+ struct c2c_hist_entry *level2_c2c;
+ bool leftmost = true;
+
+ while (*p) {
+ struct hist_entry *iter = rb_entry(*p, struct hist_entry, rb_node);
+ struct map_symbol key = detail_he->ms;
+ int64_t cmp;
+
+ key.sym = sym;
+ parent = *p;
+ cmp = c2c_function_cmp(&key, &iter->ms);
+
+ if (cmp < 0) {
+ p = &parent->rb_left;
+ } else if (cmp > 0) {
+ p = &parent->rb_right;
+ leftmost = false;
+ } else {
+ return container_of(iter, struct c2c_hist_entry, he);
+ }
+ }
+
+ /* Key by the function symbol start so all code addresses collapse. */
+ level2_c2c = c2c_child_entry__alloc(level1_he, detail_he, 1,
+ (sym && detail_he->ms.map) ?
+ map__unmap_ip(detail_he->ms.map, sym->start) :
+ hist_entry__iaddr(detail_he));
+ if (!level2_c2c)
+ return NULL;
+
+ /* Key this level by the looked-up symbol, not detail_he's. */
+ level2_c2c->he.ms.sym = sym;
+ if (level2_c2c->he.mem_info)
+ mem_info__iaddr(level2_c2c->he.mem_info)->ms.sym = sym;
+
+ c2c_child_entry__insert(level1_he, &level2_c2c->he, p, parent, leftmost);
+
+ return level2_c2c;
+}
+
+/* Level 3: one source cacheline where the L1/L2 functions contend. */
+static __maybe_unused struct c2c_hist_entry *
+c2c_function_hists__level3_entry(struct c2c_hist_entry *level2_c2c,
+ struct c2c_hist_entry *cacheline_src_he)
+{
+ struct hist_entry *level2_he = &level2_c2c->he;
+ struct rb_node **p = &level2_he->hroot_out.rb_root.rb_node;
+ struct rb_node *parent = NULL;
+ struct c2c_hist_entry *level3_c2c;
+ bool leftmost = true;
+
+ while (*p) {
+ struct c2c_hist_entry *iter_c2c =
+ rb_entry(*p, struct c2c_hist_entry, he.rb_node);
+
+ parent = *p;
+ if (cacheline_src_he->cacheline_idx < iter_c2c->cacheline_idx) {
+ p = &parent->rb_left;
+ } else if (cacheline_src_he->cacheline_idx > iter_c2c->cacheline_idx) {
+ p = &parent->rb_right;
+ leftmost = false;
+ } else {
+ return iter_c2c;
+ }
+ }
+
+ level3_c2c = c2c_child_entry__alloc(level2_he, &cacheline_src_he->he, 2,
+ hist_entry__iaddr(&cacheline_src_he->he));
+ if (!level3_c2c)
+ return NULL;
+ level3_c2c->cacheline_idx = cacheline_src_he->cacheline_idx;
+
+ c2c_child_entry__insert(level2_he, &level3_c2c->he, p, parent, leftmost);
+
+ return level3_c2c;
+}
+
+struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he_selection)
+{
+ struct c2c_hist_entry *c2c_he;
+ struct rb_node *nd;
+
+ if (!c2c_ext.cl_hists || !he_selection || !he_selection->parent_he ||
+ !he_selection->parent_he->parent_he)
+ return NULL;
+
+ c2c_he = container_of(he_selection, struct c2c_hist_entry, he);
+
+ for (nd = rb_first_cached(&c2c_ext.cl_hists->hists.entries); nd;
+ nd = rb_next(nd)) {
+ struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
+ struct c2c_hist_entry *cacheline_he;
+
+ if (he->filtered)
+ continue;
+
+ cacheline_he = container_of(he, struct c2c_hist_entry, he);
+ if (cacheline_he->hists &&
+ cacheline_he->cacheline_idx == c2c_he->cacheline_idx)
+ return he;
+ }
+
+ return NULL;
+}
diff --git a/tools/perf/util/c2c.h b/tools/perf/util/c2c.h
index bd0c9d1c9a1a..f95e636edc0f 100644
--- a/tools/perf/util/c2c.h
+++ b/tools/perf/util/c2c.h
@@ -98,4 +98,6 @@ struct c2c_fmt {
void c2c_fmt_free(struct perf_hpp_fmt *fmt);
bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b);
+struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he);
+
#endif /* __PERF_UTIL_C2C_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v9 7/9] perf c2c: build and finalize the function view hierarchy
2026-08-17 9:46 [PATCH v9 0/9] perf c2c: add a function view Jiebin Sun
` (5 preceding siblings ...)
2026-08-17 9:46 ` [PATCH v9 6/9] perf c2c: add function view hierarchy entry creation Jiebin Sun
@ 2026-08-17 9:46 ` Jiebin Sun
2026-08-17 9:54 ` sashiko-bot
2026-08-17 21:11 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 8/9] perf c2c: add function view browser UI and cacheline detail Jiebin Sun
2026-08-17 9:46 ` [PATCH v9 9/9] perf c2c: document function view in perf-c2c man page Jiebin Sun
8 siblings, 2 replies; 30+ messages in thread
From: Jiebin Sun @ 2026-08-17 9:46 UTC (permalink / raw)
To: namhyung
Cc: acme, mingo, peterz, adrian.hunter, alexander.shishkin, irogers,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, jiebin.sun, linux-perf-users,
linux-kernel
Add the builder that walks the top-level cacheline entries and, for each
read-side function, correlates the functions that write the same lines
(level 2) and the specific cachelines they contend over (level 3) within
each retained detail histogram. Aggregate the write traffic per contending
function, resort by store count, and prune writers/functions with no
contention. The finalize pass then computes the Cycles % denominator from
the surviving level-1 entries after pruning, so the column shows each
function's share of the functions retained in the table rather than of the
whole recording -- the semantics documented for Cycles % in perf-c2c.txt.
Expose c2c_function__build() and c2c_function__reset() for the TUI front
end added by the next patch. The builder requires iaddr in the cacheline
coalescing fields and returns the completed hists through an output
argument. Validate the inputs before replacing an existing model.
Function-view entries do not carry callchains. Suppress callchain handling
while building and tearing down the model so the common API does not depend
on the caller's current callchain setting.
Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
---
tools/perf/util/c2c-function.c | 530 ++++++++++++++++++++++++++++++++-
tools/perf/util/c2c.h | 10 +
2 files changed, 525 insertions(+), 15 deletions(-)
diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
index c005fb5454a7..5b6a06a5a067 100644
--- a/tools/perf/util/c2c-function.c
+++ b/tools/perf/util/c2c-function.c
@@ -45,9 +45,9 @@ struct c2c_function_model {
bool symbol_full;
};
-static struct c2c_function_model c2c_ext __maybe_unused;
+static struct c2c_function_model c2c_ext;
-static inline __maybe_unused u64 c2c_hitm_count(const struct c2c_stats *stats)
+static inline u64 c2c_hitm_count(const struct c2c_stats *stats)
{
return stats->tot_hitm;
}
@@ -82,7 +82,7 @@ static int64_t c2c_function_cmp(const struct map_symbol *left,
return _sort__sym_cmp(left->sym, right->sym);
}
-static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
+static inline u64 hist_entry__iaddr(struct hist_entry *he)
{
if (he->mem_info)
return mem_info__iaddr(he->mem_info)->addr;
@@ -219,7 +219,7 @@ static u64 c2c_hist_entry__cycles(struct c2c_hist_entry *c2c_he)
}
/* Sum c2c_hist_entry__cycles() across all level-1 entries. */
-static u64 __maybe_unused c2c_ext__total_cycles(void)
+static u64 c2c_ext__total_cycles(void)
{
struct rb_node *nd;
u64 total = 0;
@@ -359,7 +359,7 @@ cycles_percent_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
c2c_he = container_of(he, struct c2c_hist_entry, he);
fn_cycles = c2c_hist_entry__cycles(c2c_he);
- /* Populated by build_function_view_hierarchy() once the L1 tree is built. */
+ /* Populated by c2c_function__build() once the L1 tree is built. */
total_cycles = c2c_ext.total_cycles;
pct = total_cycles > 0 ? (double)fn_cycles / total_cycles * 100.0 : 0.0;
@@ -665,7 +665,7 @@ function_hpp_list__parse(struct perf_hpp_list *hpp_list,
return ret;
}
-static int __maybe_unused
+static int
c2c_function_hists__init(struct c2c_hists *hists,
const char *sort,
int nr_header_lines,
@@ -680,7 +680,7 @@ c2c_function_hists__init(struct c2c_hists *hists,
return function_hpp_list__parse(&hists->list, /*output=*/NULL, sort, env);
}
-static int __maybe_unused
+static int
c2c_function_hists__reinit(struct c2c_hists *c2c_hists,
const char *output,
const char *sort,
@@ -730,8 +730,8 @@ static void c2c_stats_merge(struct stats *dest, const struct stats *src)
}
/* Merge compute_stats during function aggregation. */
-static void __maybe_unused c2c_add_cstats(struct compute_stats *dest,
- const struct compute_stats *src)
+static void c2c_add_cstats(struct compute_stats *dest,
+ const struct compute_stats *src)
{
c2c_stats_merge(&dest->rmt_hitm, &src->rmt_hitm);
c2c_stats_merge(&dest->lcl_hitm, &src->lcl_hitm);
@@ -740,8 +740,8 @@ static void __maybe_unused c2c_add_cstats(struct compute_stats *dest,
c2c_stats_merge(&dest->load, &src->load);
}
-static bool __maybe_unused hist_entry__add_c2c_stats(struct hist_entry *he,
- const struct c2c_stats *stats)
+static bool hist_entry__add_c2c_stats(struct hist_entry *he,
+ const struct c2c_stats *stats)
{
u64 nr_events = c2c_hitm_count(stats) + stats->rmt_peer + stats->lcl_peer;
u64 weight1 = c2c_hitm_count(stats);
@@ -832,7 +832,7 @@ static void c2c_he__free_hierarchy(struct hist_entry *he)
* line, so this is mainly a safety net. Returns the number of surviving
* writers.
*/
-static int __maybe_unused c2c_he__prune_empty_writers(struct hist_entry *l1_he)
+static int c2c_he__prune_empty_writers(struct hist_entry *l1_he)
{
struct rb_node *nd;
int surviving = 0;
@@ -953,7 +953,7 @@ c2c_child_entry__insert(struct hist_entry *parent_he, struct hist_entry *child_h
parent_he->leaf = false;
}
-static __maybe_unused struct hist_entry *
+static struct hist_entry *
c2c_function_hists__level1_entry(struct symbol *sym,
struct hist_entry *detail_he,
struct thread *synthetic_thread)
@@ -1029,7 +1029,7 @@ c2c_function_hists__level1_entry(struct symbol *sym,
* sort semantics. All code addresses and cachelines for the same writer
* function aggregate into one row.
*/
-static __maybe_unused struct c2c_hist_entry *
+static struct c2c_hist_entry *
c2c_function_hists__level2_entry(struct c2c_hist_entry *level1_c2c,
struct symbol *sym, struct hist_entry *detail_he)
{
@@ -1077,7 +1077,7 @@ c2c_function_hists__level2_entry(struct c2c_hist_entry *level1_c2c,
}
/* Level 3: one source cacheline where the L1/L2 functions contend. */
-static __maybe_unused struct c2c_hist_entry *
+static struct c2c_hist_entry *
c2c_function_hists__level3_entry(struct c2c_hist_entry *level2_c2c,
struct c2c_hist_entry *cacheline_src_he)
{
@@ -1140,3 +1140,503 @@ struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he_selection)
return NULL;
}
+
+/*
+ * Re-sort child entries of @parent_he by total store count, descending.
+ */
+static void c2c_he__resort_by_stores(struct hist_entry *parent_he)
+{
+ struct rb_root_cached new_root = RB_ROOT_CACHED;
+ struct rb_node *nd;
+
+ if (!parent_he->has_children)
+ return;
+
+ /* Extract all nodes and re-insert sorted by displayed store count */
+ while ((nd = rb_first_cached(&parent_he->hroot_out))) {
+ struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
+ u64 he_store = hist_entry__displayed_stores(he);
+ struct rb_node **p = &new_root.rb_root.rb_node;
+ struct rb_node *parent = NULL;
+ bool leftmost = true;
+ int cmp;
+
+ /* Remove from current tree */
+ rb_erase_cached(&he->rb_node, &parent_he->hroot_out);
+
+ /*
+ * Insert sorted by store count, descending. Use the displayed
+ * store count so a level-1 function and level-2 writer (whose own
+ * stats.store is 0 / partial) sort by the aggregated write traffic
+ * beneath them, not by their own store field.
+ */
+ while (*p) {
+ struct hist_entry *iter = rb_entry(*p, struct hist_entry, rb_node);
+ u64 iter_store = hist_entry__displayed_stores(iter);
+
+ parent = *p;
+ if (he_store != iter_store) {
+ cmp = he_store > iter_store ? -1 : 1;
+ } else {
+ /* Stable tie-break: instruction address, name, then cacheline. */
+ u64 a = hist_entry__iaddr(he), b = hist_entry__iaddr(iter);
+
+ if (a != b)
+ cmp = a < b ? -1 : 1;
+ else if (he->ms.sym && iter->ms.sym)
+ cmp = strcmp(he->ms.sym->name,
+ iter->ms.sym->name);
+ else
+ cmp = (iter->ms.sym ? 1 : 0) - (he->ms.sym ? 1 : 0);
+
+ if (!cmp) {
+ struct c2c_hist_entry *he_c2c;
+ struct c2c_hist_entry *iter_c2c;
+
+ he_c2c = container_of(he, struct c2c_hist_entry, he);
+ iter_c2c = container_of(iter, struct c2c_hist_entry, he);
+ if (he_c2c->cacheline_idx != iter_c2c->cacheline_idx)
+ cmp = he_c2c->cacheline_idx <
+ iter_c2c->cacheline_idx ? -1 : 1;
+ }
+ }
+
+ if (cmp < 0) {
+ p = &parent->rb_left;
+ } else {
+ p = &parent->rb_right;
+ leftmost = false;
+ }
+ }
+
+ rb_link_node(&he->rb_node, parent, p);
+ rb_insert_color_cached(&he->rb_node, &new_root, leftmost);
+ }
+
+ parent_he->hroot_out = new_root;
+}
+
+/* Initial per-cacheline capacity for the seen[] set; grown on demand. */
+#define DEFAULT_SYMBOLS_PER_CL 64
+
+struct function_seen {
+ struct map_symbol ms;
+};
+
+static bool function_seen__find(const struct function_seen *seen, int nr,
+ const struct map_symbol *ms)
+{
+ int i;
+
+ for (i = 0; i < nr; i++) {
+ if (!c2c_function_cmp(&seen[i].ms, ms))
+ return true;
+ }
+ return false;
+}
+
+/* Aggregate stats from the cacheline-side entry @c2c_b into level 2/3 @dst. */
+static bool c2c_he__add_sharing(struct c2c_hist_entry *dst, struct c2c_hist_entry *src)
+{
+ /* Do the fallible update first so a failure leaves dst unmodified. */
+ if (!hist_entry__add_c2c_stats(&dst->he, &src->stats))
+ return false;
+
+ c2c_add_stats(&dst->stats, &src->stats);
+ c2c_add_cstats(&dst->cstats, &src->cstats);
+ return true;
+}
+
+/*
+ * Process one cacheline: for every function reading it, create/update its
+ * level-1 function entry, then for each function that writes the line
+ * add it as a level-2 writer and add this cacheline as a level-3 child.
+ */
+static int c2c_function__process_cl(struct c2c_hist_entry *cacheline_he,
+ struct thread *synthetic_thread)
+{
+ struct rb_node *nd_a, *nd_b;
+ struct function_seen *seen = NULL;
+ int nr_seen = 0, nr_alloc = 0;
+ int ret = 0;
+
+ for (nd_a = rb_first_cached(&cacheline_he->hists->hists.entries); nd_a;
+ nd_a = rb_next(nd_a)) {
+ struct hist_entry *he_a = rb_entry(nd_a, struct hist_entry, rb_node);
+ struct c2c_hist_entry *c2c_a;
+ struct hist_entry *level1_he;
+ struct c2c_hist_entry *level1_c2c;
+
+ if (!he_a->ms.sym || he_a->filtered)
+ continue;
+
+ c2c_a = container_of(he_a, struct c2c_hist_entry, he);
+ if (c2c_a->stats.load == 0)
+ continue;
+
+ level1_he = c2c_function_hists__level1_entry(he_a->ms.sym,
+ he_a, synthetic_thread);
+ if (!level1_he) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ level1_c2c = container_of(level1_he, struct c2c_hist_entry, he);
+
+ /*
+ * Aggregate every source entry into its level-1 function parent.
+ * level1_he is keyed by symbol, so all code addresses inside the
+ * same function collapse into one parent. When the cacheline view
+ * splits a function into siblings (different code addresses, or
+ * --coalesce pid/tid/dso), each sibling holds a DISJOINT slice of the
+ * traffic, so summing them here is correct accumulation, not
+ * double counting. The seen[] set below therefore guards only the
+ * subtree build (to avoid building a function's level-2/3 subtree
+ * twice for the same cacheline), never this L1 update. Update
+ * he->stat first; on failure leave the aggregates untouched.
+ */
+ if (!hist_entry__add_c2c_stats(level1_he, &c2c_a->stats)) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ c2c_add_stats(&level1_c2c->stats, &c2c_a->stats);
+ c2c_add_cstats(&level1_c2c->cstats, &c2c_a->cstats);
+ c2c_add_stats(&c2c_ext.function_hists.stats, &c2c_a->stats);
+
+ /* Build this function's subtree for this cacheline only once. */
+ if (function_seen__find(seen, nr_seen, &he_a->ms))
+ continue;
+
+ if (nr_seen == nr_alloc) {
+ struct function_seen *tmp;
+ int new_alloc = nr_alloc ? nr_alloc * 2 : DEFAULT_SYMBOLS_PER_CL;
+
+ tmp = reallocarray(seen, new_alloc, sizeof(*seen));
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ seen = tmp;
+ nr_alloc = new_alloc;
+ }
+ seen[nr_seen].ms = he_a->ms;
+ nr_seen++;
+
+ for (nd_b = rb_first_cached(&cacheline_he->hists->hists.entries); nd_b;
+ nd_b = rb_next(nd_b)) {
+ struct hist_entry *he_b = rb_entry(nd_b, struct hist_entry, rb_node);
+ struct c2c_hist_entry *c2c_b, *level2_c2c, *level3_c2c;
+
+ if (!he_b->ms.sym || he_b->filtered)
+ continue;
+
+ c2c_b = container_of(he_b, struct c2c_hist_entry, he);
+
+ /*
+ * The level-1 function contributes read-side load weight for this
+ * cacheline. Associate it with functions sampled storing to the
+ * same line.
+ * The writer can be the same function; after detail coalescing and
+ * function-level grouping there is not enough identity to attribute
+ * that case to a specific thread.
+ * Only writers are contending functions, so keep the ones
+ * that actually store into the line.
+ */
+ if (c2c_b->stats.store == 0)
+ continue;
+
+ /* Level 2: the writing function (aggregated across cachelines). */
+ level2_c2c = c2c_function_hists__level2_entry(level1_c2c, he_b->ms.sym,
+ he_b);
+ if (!level2_c2c || !c2c_he__add_sharing(level2_c2c, c2c_b)) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* Level 3: the specific cacheline they contend over. */
+ level3_c2c = c2c_function_hists__level3_entry(level2_c2c,
+ cacheline_he);
+ if (!level3_c2c || !c2c_he__add_sharing(level3_c2c, c2c_b)) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ }
+ }
+
+out:
+ free(seen);
+ return ret;
+}
+
+/*
+ * Remove a level-1 function that has no contended cachelines left. It is a
+ * normal (owned) hist_entry in function_hists, so mirror hists__delete_entry()
+ * for the no-collapse case: unlink from both trees, fix the counters, then
+ * delete. Its hroot_out is already empty after pruning.
+ */
+static void c2c_function__drop_level1(struct hist_entry *he)
+{
+ struct hists *hists = &c2c_ext.function_hists.hists;
+
+ rb_erase_cached(&he->rb_node_in, hists->entries_in);
+ rb_erase_cached(&he->rb_node, &hists->entries);
+
+ --hists->nr_entries;
+ if (!he->filtered)
+ --hists->nr_non_filtered_entries;
+
+ hist_entry__delete(he);
+}
+
+/* Length of the identity text (symbol name or cacheline address) at @he. */
+static int c2c_function__ident_len(struct hist_entry *he)
+{
+ char buf[512];
+ char *symbuf;
+ size_t size;
+ int len;
+
+ if (hist_entry__is_cacheline(he)) {
+ u64 addr = he->mem_info ?
+ cl_address(mem_info__daddr(he->mem_info)->addr, chk_double_cl) : 0;
+
+ return scnprintf(buf, sizeof(buf), "0x%" PRIx64, addr);
+ }
+
+ if (!he->ms.sym)
+ return 0;
+
+ /*
+ * Match symbol_view_entry(): sort_sym adds the cpumode prefix and, in
+ * verbose mode, the address and DSO origin before the symbol name.
+ */
+ size = strlen(he->ms.sym->name) + 64;
+ symbuf = malloc(size);
+ if (!symbuf)
+ return size - 1;
+
+ len = sort_sym.se_snprintf(he, symbuf, size, size - 1);
+ free(symbuf);
+ return len;
+}
+
+/*
+ * Grow the symbol column so the deepest, longest identity cell fits. The
+ * generic hists__calc_col_len() only measures the top-level (L1) entries; the
+ * hand-linked L2 writers and L3 cacheline addresses live in hroot_out and are
+ * never measured, so with a short L1 name the indented L2/L3 text would be
+ * truncated. Account for the per-level indent and the folded-sign prefix.
+ */
+static void c2c_function__update_symbol_width(struct hist_entry *he)
+{
+ struct hists *hists = &c2c_ext.function_hists.hists;
+ int need = he->depth * C2C_FUNC_INDENT + C2C_FUNC_FOLD_WIDTH +
+ c2c_function__ident_len(he);
+
+ if (need > hists__col_len(hists, HISTC_SYMBOL))
+ hists__set_col_len(hists, HISTC_SYMBOL, need);
+}
+
+/*
+ * Prune writers with no stores, drop functions left with no contending
+ * writer, sort the survivors by store count, then compute the global total.
+ */
+static void c2c_function__finalize(void)
+{
+ struct rb_node *nd_l1;
+
+ nd_l1 = rb_first_cached(&c2c_ext.function_hists.hists.entries);
+ while (nd_l1) {
+ struct hist_entry *he_l1 = rb_entry(nd_l1, struct hist_entry, rb_node);
+ struct rb_node *next_l1 = rb_next(nd_l1);
+ struct rb_node *nd_l2;
+
+ /* Drop writers with no stores before sorting. */
+ if (!he_l1->has_children || !c2c_he__prune_empty_writers(he_l1)) {
+ /* No contending writer: this function is not shared. */
+ c2c_function__drop_level1(he_l1);
+ nd_l1 = next_l1;
+ continue;
+ }
+
+ c2c_he__resort_by_stores(he_l1);
+ c2c_function__update_symbol_width(he_l1);
+
+ for (nd_l2 = rb_first_cached(&he_l1->hroot_out); nd_l2;
+ nd_l2 = rb_next(nd_l2)) {
+ struct hist_entry *he_l2 = rb_entry(nd_l2, struct hist_entry, rb_node);
+ struct rb_node *nd_l3;
+
+ c2c_function__update_symbol_width(he_l2);
+
+ if (he_l2->has_children)
+ c2c_he__resort_by_stores(he_l2);
+
+ for (nd_l3 = rb_first_cached(&he_l2->hroot_out); nd_l3;
+ nd_l3 = rb_next(nd_l3)) {
+ struct hist_entry *he_l3 = rb_entry(nd_l3, struct hist_entry,
+ rb_node);
+
+ c2c_function__update_symbol_width(he_l3);
+ }
+ }
+
+ nd_l1 = next_l1;
+ }
+
+ /*
+ * Compute the Cycles % denominator from the surviving level-1 entries
+ * after pruning, so the column shows each function's share of the
+ * functions retained in this table -- not of the whole recording. See
+ * the Cycles % description in perf-c2c.txt.
+ */
+ c2c_ext.total_cycles = c2c_ext__total_cycles();
+}
+
+/*
+ * Release all per-visit function-view state. Keep the hists object itself
+ * initialized so its mutex is initialized exactly once across TAB re-entry.
+ */
+void c2c_function__reset(void)
+{
+ bool saved_use_callchain = symbol_conf.use_callchain;
+
+ /*
+ * Function-view entries never carry callchains. Keep their generic
+ * destructor independent of the caller's current callchain setting.
+ */
+ symbol_conf.use_callchain = false;
+ hists__delete_all_entries(&c2c_ext.function_hists.hists);
+ if (c2c_ext.function_hists.list.fields.next)
+ perf_hpp__reset_output_field(&c2c_ext.function_hists.list);
+
+ memset(&c2c_ext.function_hists.stats, 0,
+ sizeof(c2c_ext.function_hists.stats));
+ c2c_ext.total_cycles = 0;
+ c2c_ext.cl_hists = NULL;
+ c2c_ext.cl_sort = NULL;
+ c2c_ext.symbol_full = false;
+ symbol_conf.use_callchain = saved_use_callchain;
+}
+
+static bool c2c_function__has_iaddr(const char *cl_sort)
+{
+ const char *field = cl_sort;
+
+ while (field && *field) {
+ const char *end = strchr(field, ',');
+ size_t len = end ? (size_t)(end - field) : strlen(field);
+
+ if (len == sizeof("iaddr") - 1 && !strncmp(field, "iaddr", len))
+ return true;
+ field = end ? end + 1 : NULL;
+ }
+ return false;
+}
+
+/*
+ * Build the three-level function view in a single pass over the cacheline
+ * entries:
+ * L1: read-side functions (aggregated across all their code addresses)
+ * L2: writing functions contending with each level-1 function
+ * L3: shared cachelines for each function pair
+ */
+int c2c_function__build(struct c2c_hists *cl_hists, const char *cl_sort,
+ bool symbol_full, struct hists **hists)
+{
+ static const char output_fields[] =
+ "cycles_percent,total_stores,symbol_view";
+ static bool hists_initialized;
+ struct rb_node *nd_cl;
+ bool saved_use_callchain;
+ int ret;
+
+ if (!hists)
+ return -EINVAL;
+ *hists = NULL;
+
+ if (!cl_hists || !cl_sort)
+ return -EINVAL;
+ if (!c2c_function__has_iaddr(cl_sort))
+ return -EOPNOTSUPP;
+
+ saved_use_callchain = symbol_conf.use_callchain;
+ symbol_conf.use_callchain = false;
+ c2c_function__reset();
+
+ c2c_ext.cl_hists = cl_hists;
+ c2c_ext.cl_sort = cl_sort;
+ c2c_ext.symbol_full = symbol_full;
+
+ /*
+ * __hists__init() (called by c2c_function_hists__init()) also
+ * mutex_init()s the hists lock, so only run it once for this static
+ * hists; on re-entry just re-parse the columns via reinit().
+ */
+ if (!hists_initialized) {
+ ret = c2c_function_hists__init(&c2c_ext.function_hists,
+ "symbol_view", 2, NULL);
+ hists_initialized = true;
+ } else {
+ ret = c2c_function_hists__reinit(&c2c_ext.function_hists,
+ /*output=*/NULL, "symbol_view", NULL);
+ }
+ if (ret)
+ goto out_reset;
+
+ nd_cl = rb_first_cached(&c2c_ext.cl_hists->hists.entries);
+
+ /* An empty C2C report yields an empty (but valid) function view. */
+ for (; nd_cl; nd_cl = rb_next(nd_cl)) {
+ struct hist_entry *he_cl = rb_entry(nd_cl, struct hist_entry, rb_node);
+ struct c2c_hist_entry *cacheline_he = container_of(he_cl,
+ struct c2c_hist_entry, he);
+ struct thread *synthetic_thread = he_cl->thread;
+
+ /*
+ * Detail hists are finalized only for cachelines retained by the
+ * top-level C2C filter. Among those, include any line with sharing
+ * activity, not just HITM.
+ */
+ if (he_cl->filtered ||
+ (c2c_hitm_count(&cacheline_he->stats) == 0 &&
+ cacheline_he->stats.tot_peer == 0 &&
+ cacheline_he->stats.store == 0 &&
+ cacheline_he->stats.load == 0) ||
+ !cacheline_he->hists ||
+ RB_EMPTY_ROOT(&cacheline_he->hists->hists.entries.rb_root) ||
+ !he_cl->mem_info || !synthetic_thread)
+ continue;
+
+ ret = c2c_function__process_cl(cacheline_he, synthetic_thread);
+ if (ret)
+ goto out_err;
+ }
+
+ ret = c2c_function_hists__reinit(&c2c_ext.function_hists, output_fields,
+ "cycles_percent", NULL);
+ if (ret)
+ goto out_err;
+
+ hists__collapse_resort(&c2c_ext.function_hists.hists, NULL);
+ hists__output_resort(&c2c_ext.function_hists.hists, NULL);
+
+ c2c_function__finalize();
+
+ *hists = &c2c_ext.function_hists.hists;
+ symbol_conf.use_callchain = saved_use_callchain;
+ return 0;
+
+out_err:
+ /*
+ * On error, migrate any entries still in entries_in to entries and
+ * delete them, so a later rebuild does not strand them (the top-level
+ * __hists__init() memset would otherwise lose the pointers).
+ */
+ hists__collapse_resort(&c2c_ext.function_hists.hists, NULL);
+ hists__output_resort(&c2c_ext.function_hists.hists, NULL);
+out_reset:
+ c2c_function__reset();
+ symbol_conf.use_callchain = saved_use_callchain;
+ return ret;
+}
diff --git a/tools/perf/util/c2c.h b/tools/perf/util/c2c.h
index f95e636edc0f..6548b9ba81f1 100644
--- a/tools/perf/util/c2c.h
+++ b/tools/perf/util/c2c.h
@@ -98,6 +98,16 @@ struct c2c_fmt {
void c2c_fmt_free(struct perf_hpp_fmt *fmt);
bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b);
+/*
+ * Build the function-view hierarchy. Returns -EOPNOTSUPP when @cl_sort lacks
+ * iaddr. On success, *@hists remains valid until the next
+ * c2c_function__build() or c2c_function__reset(). On failure, *@hists is
+ * NULL.
+ */
+int c2c_function__build(struct c2c_hists *cl_hists, const char *cl_sort,
+ bool symbol_full, struct hists **hists);
+void c2c_function__reset(void);
+/* Valid only between a successful build and c2c_function__reset(). */
struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he);
#endif /* __PERF_UTIL_C2C_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v9 8/9] perf c2c: add function view browser UI and cacheline detail
2026-08-17 9:46 [PATCH v9 0/9] perf c2c: add a function view Jiebin Sun
` (6 preceding siblings ...)
2026-08-17 9:46 ` [PATCH v9 7/9] perf c2c: build and finalize the function view hierarchy Jiebin Sun
@ 2026-08-17 9:46 ` Jiebin Sun
2026-08-17 9:51 ` sashiko-bot
2026-08-17 21:12 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 9/9] perf c2c: document function view in perf-c2c man page Jiebin Sun
8 siblings, 2 replies; 30+ messages in thread
From: Jiebin Sun @ 2026-08-17 9:46 UTC (permalink / raw)
To: namhyung
Cc: acme, mingo, peterz, adrian.hunter, alexander.shishkin, irogers,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, jiebin.sun, linux-perf-users,
linux-kernel
Add the browser front end: create/run/delete the hist_browser and add the
title. The d shortcut opens the existing per-cacheline detail view for the
selected level-3 cacheline. Level-3 entries retain the source cacheline
index, so the shortcut can locate the original entry without relying on a
potentially ambiguous virtual address.
Report a warning when the common model rejects a cacheline coalescing field
list without `iaddr`. Without it, the detail histograms may already have
merged samples from different functions and cannot support reliable
function attribution.
Keep visible-row accounting local to the function view by wrapping the
generic browser refresh callback and recounting the currently reachable
hierarchy before each redraw. This keeps navigation correct when a level-1
row is collapsed while level-3 descendants remain expanded, without adding
C2C-specific hooks to the shared hist_browser. Also handle Ctrl-C like the
other function-view exit keys.
Keep callchains hidden while the function browser runs, restoring the
user's setting while opening the cacheline detail view.
Wire the builder into perf_c2c__browse_function_view().
Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
---
tools/perf/builtin-c2c.c | 10 ++
tools/perf/ui/browsers/Build | 1 +
tools/perf/ui/browsers/c2c-function.c | 209 ++++++++++++++++++++++++++
tools/perf/util/c2c.h | 22 +++
4 files changed, 242 insertions(+)
create mode 100644 tools/perf/ui/browsers/c2c-function.c
diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
index 16b00a36fdfc..715b75d42f2a 100644
--- a/tools/perf/builtin-c2c.c
+++ b/tools/perf/builtin-c2c.c
@@ -2745,11 +2745,18 @@ perf_c2c_browser__new(struct hists *hists)
static int perf_c2c__hists_browse(struct hists *hists)
{
+ struct c2c_function_view_args func_args = {
+ .cl_hists = &c2c.hists,
+ .cl_sort = c2c.cl_sort,
+ .symbol_full = c2c.symbol_full,
+ .browse_cacheline = perf_c2c__browse_cacheline,
+ };
struct hist_browser *browser;
int key = -1;
static const char help[] =
" d Display cacheline details \n"
" ENTER Toggle callchains (if present) \n"
+ " TAB Switch to function view\n"
" q Quit \n";
browser = perf_c2c_browser__new(hists);
@@ -2771,6 +2778,9 @@ static int perf_c2c__hists_browse(struct hists *hists)
case 'd':
perf_c2c__browse_cacheline(browser->he_selection);
break;
+ case '\t':
+ perf_c2c__browse_function_view(&func_args);
+ break;
case '?':
ui_browser__help_window(&browser->b, help);
break;
diff --git a/tools/perf/ui/browsers/Build b/tools/perf/ui/browsers/Build
index a07489e44765..ae67a2161f7d 100644
--- a/tools/perf/ui/browsers/Build
+++ b/tools/perf/ui/browsers/Build
@@ -5,3 +5,4 @@ perf-ui-y += map.o
perf-ui-y += scripts.o
perf-ui-y += header.o
perf-ui-y += res_sample.o
+perf-ui-y += c2c-function.o
diff --git a/tools/perf/ui/browsers/c2c-function.c b/tools/perf/ui/browsers/c2c-function.c
new file mode 100644
index 000000000000..3d23e9d84981
--- /dev/null
+++ b/tools/perf/ui/browsers/c2c-function.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * C2C function browser - TUI front end for function-level sharing analysis
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <sys/ttydefaults.h>
+#include <linux/rbtree.h>
+#include <linux/zalloc.h>
+
+#include "../browser.h"
+#include "../keysyms.h"
+#include "../libslang.h"
+#include "../ui.h"
+#include "../../util/c2c.h"
+#include "../../util/debug.h"
+#include "../../util/hist.h"
+#include "../../util/symbol.h"
+#include "hists.h"
+
+struct c2c_function_browser {
+ struct hist_browser hb;
+ unsigned int (*orig_refresh)(struct ui_browser *browser);
+ int (*browse_cacheline)(struct hist_entry *he);
+};
+
+/*
+ * Count visible entries in @root, descending only through visible, unfolded
+ * parents. Match hists__filter_entries(), which drives generic browser
+ * navigation, so the count cannot include rows the browser skips.
+ */
+static u64
+c2c_function__nr_visible_rows(struct rb_root_cached *root, float min_pcnt)
+{
+ struct rb_node *nd;
+ u64 rows = 0;
+
+ for (nd = rb_first_cached(root); nd; nd = rb_next(nd)) {
+ struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
+
+ /*
+ * The generic refresh folds filtered parents and therefore hides
+ * their subtree. A percentage-rejected parent is merely skipped;
+ * if it is unfolded, qualifying descendants are still rendered.
+ */
+ if (he->filtered)
+ continue;
+
+ if (hist_entry__get_percent_limit(he) >= min_pcnt)
+ rows++;
+ if (he->has_children && he->unfolded)
+ rows += c2c_function__nr_visible_rows(&he->hroot_out,
+ min_pcnt);
+ }
+ return rows;
+}
+
+static void
+c2c_function_browser__update_nr_entries(struct c2c_function_browser *browser)
+{
+ u64 nr_entries;
+
+ nr_entries = c2c_function__nr_visible_rows(&browser->hb.hists->entries,
+ browser->hb.min_pcnt);
+ browser->hb.nr_non_filtered_entries = nr_entries;
+ browser->hb.b.nr_entries = nr_entries;
+}
+
+static unsigned int c2c_function_browser__refresh(struct ui_browser *ui_browser)
+{
+ struct hist_browser *hist_browser = container_of(ui_browser, struct hist_browser, b);
+ struct c2c_function_browser *browser;
+
+ browser = container_of(hist_browser, struct c2c_function_browser, hb);
+ c2c_function_browser__update_nr_entries(browser);
+ return browser->orig_refresh(ui_browser);
+}
+
+static int c2c_function_browser__title(struct hist_browser *browser,
+ char *bf, size_t size)
+{
+ scnprintf(bf, size,
+ "Shared Data Functions Table (%" PRIu64 " entries, sorted on Cycles %%)",
+ browser->hists->nr_non_filtered_entries);
+ return 0;
+}
+
+static struct c2c_function_browser *
+c2c_function_browser__new(struct hists *hists,
+ int (*browse_cacheline)(struct hist_entry *he))
+{
+ struct c2c_function_browser *browser;
+
+ if (!hists)
+ return NULL;
+
+ browser = zalloc(sizeof(*browser));
+ if (!browser)
+ return NULL;
+
+ hist_browser__init(&browser->hb, hists);
+ browser->orig_refresh = browser->hb.b.refresh;
+ browser->hb.b.refresh = c2c_function_browser__refresh;
+ browser->browse_cacheline = browse_cacheline;
+
+ browser->hb.title = c2c_function_browser__title;
+ browser->hb.c2c_filter = true;
+ browser->hb.show_headers = true;
+ /* Keep title line count consistent with forcing headers on. */
+ browser->hb.b.extra_title_lines = hists->hpp_list->nr_header_lines;
+ browser->hb.min_pcnt = 0.0;
+
+ return browser;
+}
+
+static void c2c_function_browser__delete(struct c2c_function_browser *browser)
+{
+ free(browser);
+}
+
+static int
+c2c_browser__browse_cacheline(struct c2c_function_browser *browser,
+ struct hist_entry *he_selection)
+{
+ struct hist_entry *he = c2c_function__find_cacheline(he_selection);
+
+ return he ? browser->browse_cacheline(he) : -1;
+}
+
+int perf_c2c__browse_function_view(struct c2c_function_view_args *args)
+{
+ struct c2c_function_browser *browser;
+ struct hists *hists;
+ bool saved_use_callchain = symbol_conf.use_callchain;
+ int key, ret;
+ static const char help[] =
+ " d Display details for the selected level-3 cacheline\n"
+ " e/+ Expand/collapse the selected entry\n"
+ " TAB/ESC/q/^C Return to the cacheline view\n";
+
+ if (!args || !args->cl_hists || !args->browse_cacheline)
+ return -EINVAL;
+
+ /*
+ * Function view does not display callchains; cacheline detail temporarily
+ * restores them.
+ */
+ symbol_conf.use_callchain = false;
+
+ ret = c2c_function__build(args->cl_hists, args->cl_sort,
+ args->symbol_full, &hists);
+ if (ret) {
+ if (ret == -EOPNOTSUPP)
+ ui__warning("The function view requires iaddr in --coalesce.\n");
+ else
+ ui__error("Failed to build function view hierarchy (ret=%d)\n", ret);
+ goto out;
+ }
+
+ browser = c2c_function_browser__new(hists, args->browse_cacheline);
+ if (!browser) {
+ ret = -ENOMEM;
+ goto out_reset;
+ }
+
+ /* Reset abort key so we can receive Ctrl-C as a key. */
+ SLang_reset_tty();
+ SLang_init_tty(0, 0, 0);
+ SLtty_set_suspend_state(true);
+
+ while (1) {
+ c2c_function_browser__update_nr_entries(browser);
+ key = hist_browser__run(&browser->hb, "? - help", true, 0);
+
+ switch (key) {
+ case 'q':
+ case K_TAB:
+ case K_ESC:
+ case CTRL('c'):
+ goto browser_done;
+ case 'd':
+ /* Cacheline detail honors the user's callchain setting. */
+ symbol_conf.use_callchain = saved_use_callchain;
+ c2c_browser__browse_cacheline(browser, browser->hb.he_selection);
+ /*
+ * Preserve any toggle made in the detail view, then
+ * re-disable callchain for the function view.
+ */
+ saved_use_callchain = symbol_conf.use_callchain;
+ symbol_conf.use_callchain = false;
+ break;
+ case '?':
+ ui_browser__help_window(&browser->hb.b, help);
+ break;
+ default:
+ break;
+ }
+ }
+
+browser_done:
+ c2c_function_browser__delete(browser);
+out_reset:
+ c2c_function__reset();
+out:
+ symbol_conf.use_callchain = saved_use_callchain;
+ return ret;
+}
diff --git a/tools/perf/util/c2c.h b/tools/perf/util/c2c.h
index 6548b9ba81f1..53f024e25d99 100644
--- a/tools/perf/util/c2c.h
+++ b/tools/perf/util/c2c.h
@@ -110,4 +110,26 @@ void c2c_function__reset(void);
/* Valid only between a successful build and c2c_function__reset(). */
struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he);
+/* Inputs and TUI callback supplied by the c2c command. */
+struct c2c_function_view_args {
+ /* Source cacheline histograms used by the common model. */
+ struct c2c_hists *cl_hists;
+ /* --coalesce field list, used to require iaddr. */
+ const char *cl_sort;
+ /* Do not cap long symbol names. */
+ bool symbol_full;
+ /* Open the cacheline detail view for @he. */
+ int (*browse_cacheline)(struct hist_entry *he);
+};
+
+#ifdef HAVE_SLANG_SUPPORT
+int perf_c2c__browse_function_view(struct c2c_function_view_args *args);
+#else
+static inline int
+perf_c2c__browse_function_view(struct c2c_function_view_args *args __maybe_unused)
+{
+ return 0;
+}
+#endif
+
#endif /* __PERF_UTIL_C2C_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v9 9/9] perf c2c: document function view in perf-c2c man page
2026-08-17 9:46 [PATCH v9 0/9] perf c2c: add a function view Jiebin Sun
` (7 preceding siblings ...)
2026-08-17 9:46 ` [PATCH v9 8/9] perf c2c: add function view browser UI and cacheline detail Jiebin Sun
@ 2026-08-17 9:46 ` Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
2026-08-17 21:16 ` Ian Rogers
8 siblings, 2 replies; 30+ messages in thread
From: Jiebin Sun @ 2026-08-17 9:46 UTC (permalink / raw)
To: namhyung
Cc: acme, mingo, peterz, adrian.hunter, alexander.shishkin, irogers,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, jiebin.sun, linux-perf-users,
linux-kernel
Describe the function view hierarchy (read-side function -> contending
writer function -> shared cachelines), the per-level indentation, and the
keys, with a worked example.
Document that reliable function attribution requires `iaddr` in
`--coalesce`, that the reader and writer may be the same function, and why
the coalesced function view cannot distinguish same-thread from
different-thread accesses in that case. Also document that verbose mode
includes code addresses in function rows.
Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
---
tools/perf/Documentation/perf-c2c.txt | 71 +++++++++++++++++++++++++++
1 file changed, 71 insertions(+)
diff --git a/tools/perf/Documentation/perf-c2c.txt b/tools/perf/Documentation/perf-c2c.txt
index e57a122b8719..8775889bc0a3 100644
--- a/tools/perf/Documentation/perf-c2c.txt
+++ b/tools/perf/Documentation/perf-c2c.txt
@@ -365,6 +365,77 @@ TUI OUTPUT
The TUI output provides interactive interface to navigate
through cachelines list and to display offset details.
+Pressing the 'TAB' key in the cacheline view switches to the function
+view. The function view shows a three-level hierarchy of the symbolized
+entries retained in the cacheline view, organized around functions rather
+than cachelines. Levels 1 and 2 normally show function names, while level 3
+shows cacheline addresses. Lower levels are indented beneath their parents.
+Verbose mode also includes code addresses in function rows, and code addresses
+remain available in the per-cacheline detail view ('d').
+
+The function view requires `iaddr` in the cacheline coalescing fields. If
+`--coalesce` omits it, TAB reports that the view is unavailable rather than
+attributing already-coalesced samples to an arbitrary function.
+
+ Level 1: the read-side function, sorted by Cycles % (estimated load
+ cycles: HITM, peer-snoop and other-load cycles)
+ Level 2: the functions sampled writing the shared lines read by the
+ level-1 function, sorted by store count. This can be the same
+ function when it has both read and write samples
+ Level 3: the specific cachelines shared by the reader/writer pair
+
+The Cycles % value is the function's share of event-provided load
+latency/weight estimates from cacheline-detail entries retained in the
+current view. It can include non-HITM and non-peer loads coalesced into
+entries that pass the C2C filter, so it is not a pure contention-cycle
+percentage. The share is relative to the functions and entries retained
+for the current report and is not comparable across recordings or different
+`--coalesce` settings.
+
+The store count on a level-1 row is the number of sampled stores by writers
+shown in the function view into the cachelines that function reads, including
+stores from the same function. It decomposes into the level-2 writer rows;
+each level-2 count in turn decomposes into that writer's stores on its level-3
+cachelines. A level-3 count is therefore not the cacheline's total store
+count. The level-1 value is not the number of stores made by the reader and
+is not additive across level-1 rows: two functions reading the same line each
+carry the stores into that line.
+
+Each function aggregates all of its code addresses into a single entry,
+and a level-2 writer aggregates all of its shared cachelines, so a
+reader/writer pair is a single row with its total shown -- there is no
+need to sum a writer's traffic across cachelines by hand.
+
+In the function view the 'd' key opens the detail view of the selected
+level-3 cacheline, 'e'/'+' expands or collapses the current entry, and 'TAB',
+'ESC', 'q' or Ctrl-C returns to the cacheline view.
+
+For example, with the first two read-side functions collapsed and
+dequeue_pushable_task expanded to show the functions writing the lines it
+reads -- two of which are further expanded to their individual cachelines:
+
+ Shared Data Functions Table (19 entries, sorted on Cycles %)
+ Cycles Store
+ % count Function / Contending function / Cacheline
+ ----------------------------------------------------------------------
+ + 35.67% 876 + [k] cpupri_set
+ + 24.31% 424 + [k] pull_rt_task
+ - 16.53% 555 - [k] dequeue_pushable_task
+ 145 - [k] pull_rt_task
+ 145 0xff2d0082809da080
+ 139 - [k] enqueue_pushable_task
+ 70 0xff2d00a2071f9640
+ 69 0xff2d0082809da000
+
+Here dequeue_pushable_task pays 16.53% of the estimated read-side load-cycle
+cost. Its store count decomposes into its level-2 writers, and each writer's
+count decomposes into its level-3 cachelines: pull_rt_task's 145 stores fall
+on a single line, while enqueue_pushable_task's 139 stores split across two
+lines (70 and 69). A writer can be the same function as the reader when it
+has both read and write samples; after cacheline coalescing and
+function-level grouping, the view cannot distinguish same-thread accesses
+from different threads running the same function.
+
For details please refer to the help window by pressing '?' key.
CREDITS
--
2.52.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* Re: [PATCH v9 2/9] perf c2c: add function view model skeleton
2026-08-17 9:46 ` [PATCH v9 2/9] perf c2c: add function view model skeleton Jiebin Sun
@ 2026-08-17 9:48 ` sashiko-bot
2026-08-17 20:53 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: sashiko-bot @ 2026-08-17 9:48 UTC (permalink / raw)
To: Jiebin Sun; +Cc: linux-perf-users
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] symbol_name_equal() uses host-architecture logic for cross-platform analysis.
--
--- Patch [2]: [PATCH v9 2/9] perf c2c: add function view model skeleton ---
commit 4a0efb41e4b472e806fe1942f44d90ab0a7857c6
Author: Jiebin Sun <jiebin.sun@intel.com>
perf c2c: add function view model skeleton
This commit adds the initial common model for the c2c function view. It
introduces model state and small helpers shared by hierarchy construction
and formatting, building the model independently from util/.
> diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
> new file mode 100644
> index 0000000000000..ca82425a28dc3
> --- /dev/null
> +++ b/tools/perf/util/c2c-function.c
[ ... ]
> +static inline __maybe_unused bool symbol_name_equal(struct symbol *a, struct symbol *b)
> +{
> + /* Two unknown symbols compare equal, matching cmp_null() in util/sort.c. */
> + if (!a || !b)
> + return a == b;
> + return arch__compare_symbol_names(a->name, b->name) == 0;
[Severity: Medium]
Does this break cross-platform analysis?
Calling arch__compare_symbol_names() delegates symbol comparison to a
host-specific weak function rather than checking the target architecture.
If someone analyzes a perf.data file recorded on a different architecture
(like PowerPC) on an x86 host using perf c2c, could this lead to incorrect
symbol matching and inaccurate profiling reports because functions fail to
coalesce properly?
Would it be safer to query the e_machine constant dynamically via the
perf_env or session to handle architectural variations here?
> +}
> +
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260817094623.3288456-1-jiebin.sun@intel.com?part=2
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 9/9] perf c2c: document function view in perf-c2c man page
2026-08-17 9:46 ` [PATCH v9 9/9] perf c2c: document function view in perf-c2c man page Jiebin Sun
@ 2026-08-17 9:49 ` sashiko-bot
2026-08-17 21:16 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: sashiko-bot @ 2026-08-17 9:49 UTC (permalink / raw)
To: Jiebin Sun; +Cc: linux-perf-users
> Describe the function view hierarchy (read-side function -> contending
> writer function -> shared cachelines), the per-level indentation, and the
> keys, with a worked example.
>
> Document that reliable function attribution requires `iaddr` in
> `--coalesce`, that the reader and writer may be the same function, and why
> the coalesced function view cannot distinguish same-thread from
> different-thread accesses in that case. Also document that verbose mode
> includes code addresses in function rows.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260817094623.3288456-1-jiebin.sun@intel.com?part=9
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 3/9] perf c2c: add column rendering for function view
2026-08-17 9:46 ` [PATCH v9 3/9] perf c2c: add column rendering for function view Jiebin Sun
@ 2026-08-17 9:49 ` sashiko-bot
2026-08-17 20:58 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: sashiko-bot @ 2026-08-17 9:49 UTC (permalink / raw)
To: Jiebin Sun; +Cc: linux-perf-users
> Add renderers for the function view's Cycles %, Store count, and
> hierarchy identity columns. The identity column renders the read-side
> function, contending writer, or cacheline, with indentation for the
> hierarchy level. Also add width and header helpers, estimated-cycle
> calculation, comparators, and the dimension table that ties them together.
>
> Clamp the identity renderer's returned length to its local buffer before
> using it for pointer and padding calculations. This handles snprintf-style
> would-have-been lengths without changing normal output.
>
> The next patch connects these dimensions to the view's HPP lists, so the
> symbols used only there are temporarily marked __maybe_unused.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260817094623.3288456-1-jiebin.sun@intel.com?part=3
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 5/9] perf c2c: add function view stats merge and memory management
2026-08-17 9:46 ` [PATCH v9 5/9] perf c2c: add function view stats merge and memory management Jiebin Sun
@ 2026-08-17 9:49 ` sashiko-bot
2026-08-17 21:05 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: sashiko-bot @ 2026-08-17 9:49 UTC (permalink / raw)
To: Jiebin Sun; +Cc: linux-perf-users
> Add the per-entry stats/cstats aggregation helpers and hierarchy teardown.
> Child common fields are released through hist_entry__delete(), while the
> function-view free callback handles the private child tree and containing
> allocation. Also add a helper for pruning writer entries with no stores or
> cacheline children.
>
> These are used by the entry-creation and builder patches that follow and
> are __maybe_unused until then.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260817094623.3288456-1-jiebin.sun@intel.com?part=5
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 6/9] perf c2c: add function view hierarchy entry creation
2026-08-17 9:46 ` [PATCH v9 6/9] perf c2c: add function view hierarchy entry creation Jiebin Sun
@ 2026-08-17 9:49 ` sashiko-bot
2026-08-17 21:06 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: sashiko-bot @ 2026-08-17 9:49 UTC (permalink / raw)
To: Jiebin Sun; +Cc: linux-perf-users
> Add the entry-creation layer: owned-reference child allocation and
> insertion, and the level-1/2/3 lookup-or-create functions keyed by
> function symbol (level 1 read-side, level 2 writer) and by the source
> cacheline's existing index (level 3).
>
> Give synthetic children normal entry operations and acquire their thread
> and map-symbol references. This lets the hierarchy teardown use
> hist_entry__delete() for the common fields while the function-view free
> callback handles the private child tree and containing allocation.
>
> Reuse cacheline_idx to preserve the source entry identity without adding
> function-view-only state. Add c2c_function__find_cacheline() to locate the
> original cacheline entry by the same index.
>
> These are driven by the hierarchy builder in the next patch and are
> __maybe_unused until then.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260817094623.3288456-1-jiebin.sun@intel.com?part=6
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 4/9] perf c2c: add HPP list parsing for function view columns
2026-08-17 9:46 ` [PATCH v9 4/9] perf c2c: add HPP list parsing for function view columns Jiebin Sun
@ 2026-08-17 9:50 ` sashiko-bot
2026-08-17 21:04 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: sashiko-bot @ 2026-08-17 9:50 UTC (permalink / raw)
To: Jiebin Sun; +Cc: linux-perf-users
> Add the parser that builds the function view's local HPP output and
> sort lists from field strings. This includes dimension lookup, comparator
> wrappers, c2c_fmt allocation, and the initialization entry points used by
> the hierarchy builder.
>
> The generic perf_hpp__setup_output_field() registers formats on the global
> perf_hpp_list. Using it here would leave the function view's local list
> without output columns and modify the cacheline view's list instead. Add
> c2c_function_hists__setup_output_field() to append sort keys to the local
> output list.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260817094623.3288456-1-jiebin.sun@intel.com?part=4
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 8/9] perf c2c: add function view browser UI and cacheline detail
2026-08-17 9:46 ` [PATCH v9 8/9] perf c2c: add function view browser UI and cacheline detail Jiebin Sun
@ 2026-08-17 9:51 ` sashiko-bot
2026-08-17 21:12 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: sashiko-bot @ 2026-08-17 9:51 UTC (permalink / raw)
To: Jiebin Sun; +Cc: linux-perf-users
> Add the browser front end: create/run/delete the hist_browser and add the
> title. The d shortcut opens the existing per-cacheline detail view for the
> selected level-3 cacheline. Level-3 entries retain the source cacheline
> index, so the shortcut can locate the original entry without relying on a
> potentially ambiguous virtual address.
>
> Report a warning when the common model rejects a cacheline coalescing field
> list without `iaddr`. Without it, the detail histograms may already have
> merged samples from different functions and cannot support reliable
> function attribution.
>
> Keep visible-row accounting local to the function view by wrapping the
> generic browser refresh callback and recounting the currently reachable
> hierarchy before each redraw. This keeps navigation correct when a level-1
> row is collapsed while level-3 descendants remain expanded, without adding
> C2C-specific hooks to the shared hist_browser. Also handle Ctrl-C like the
> other function-view exit keys.
>
> Keep callchains hidden while the function browser runs, restoring the
> user's setting while opening the cacheline detail view.
>
> Wire the builder into perf_c2c__browse_function_view().
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260817094623.3288456-1-jiebin.sun@intel.com?part=8
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 7/9] perf c2c: build and finalize the function view hierarchy
2026-08-17 9:46 ` [PATCH v9 7/9] perf c2c: build and finalize the function view hierarchy Jiebin Sun
@ 2026-08-17 9:54 ` sashiko-bot
2026-08-17 21:11 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: sashiko-bot @ 2026-08-17 9:54 UTC (permalink / raw)
To: Jiebin Sun; +Cc: linux-perf-users
> Add the builder that walks the top-level cacheline entries and, for each
> read-side function, correlates the functions that write the same lines
> (level 2) and the specific cachelines they contend over (level 3) within
> each retained detail histogram. Aggregate the write traffic per contending
> function, resort by store count, and prune writers/functions with no
> contention. The finalize pass then computes the Cycles % denominator from
> the surviving level-1 entries after pruning, so the column shows each
> function's share of the functions retained in the table rather than of the
> whole recording -- the semantics documented for Cycles % in perf-c2c.txt.
>
> Expose c2c_function__build() and c2c_function__reset() for the TUI front
> end added by the next patch. The builder requires iaddr in the cacheline
> coalescing fields and returns the completed hists through an output
> argument. Validate the inputs before replacing an existing model.
>
> Function-view entries do not carry callchains. Suppress callchain handling
> while building and tearing down the model so the common API does not depend
> on the caller's current callchain setting.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260817094623.3288456-1-jiebin.sun@intel.com?part=7
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 1/9] perf c2c: extract shared data structures into util/c2c.h
2026-08-17 9:46 ` [PATCH v9 1/9] perf c2c: extract shared data structures into util/c2c.h Jiebin Sun
@ 2026-08-17 9:59 ` sashiko-bot
2026-08-17 20:34 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: sashiko-bot @ 2026-08-17 9:59 UTC (permalink / raw)
To: Jiebin Sun; +Cc: linux-perf-users
> The function browser belongs in libperf-ui.a, but that archive is also
> linked into python/perf.so, where builtin command objects are unavailable.
> The browser therefore cannot depend on types or callbacks owned by
> builtin-c2c.c.
>
> Move c2c_hists, compute_stats, c2c_hist_entry, and the shared column
> formatting definitions from builtin-c2c.c to a new util/c2c.h. Move
> c2c_fmt_free() and c2c_fmt_equal() to a new util/c2c.c.
>
> Keep struct perf_c2c, the command instance, and
> perf_c2c__browse_cacheline() private to builtin-c2c.c.
>
> No functional change.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260817094623.3288456-1-jiebin.sun@intel.com?part=1
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 1/9] perf c2c: extract shared data structures into util/c2c.h
2026-08-17 9:46 ` [PATCH v9 1/9] perf c2c: extract shared data structures into util/c2c.h Jiebin Sun
2026-08-17 9:59 ` sashiko-bot
@ 2026-08-17 20:34 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: Ian Rogers @ 2026-08-17 20:34 UTC (permalink / raw)
To: Jiebin Sun
Cc: namhyung, acme, mingo, peterz, adrian.hunter, alexander.shishkin,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, linux-perf-users, linux-kernel
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
>
> The function browser belongs in libperf-ui.a, but that archive is also
> linked into python/perf.so, where builtin command objects are unavailable.
> The browser therefore cannot depend on types or callbacks owned by
> builtin-c2c.c.
>
> Move c2c_hists, compute_stats, c2c_hist_entry, and the shared column
> formatting definitions from builtin-c2c.c to a new util/c2c.h. Move
> c2c_fmt_free() and c2c_fmt_equal() to a new util/c2c.c.
>
> Keep struct perf_c2c, the command instance, and
> perf_c2c__browse_cacheline() private to builtin-c2c.c.
>
> No functional change.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> Cc: Adrian Hunter <adrian.hunter@intel.com>
> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> Cc: Ian Rogers <irogers@google.com>
> Cc: Ingo Molnar <mingo@redhat.com>
> Cc: James Clark <james.clark@linaro.org>
> Cc: Jiri Olsa <jolsa@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Namhyung Kim <namhyung@kernel.org>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Thomas Falcon <thomas.falcon@intel.com>
> Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/builtin-c2c.c | 105 ++-------------------------------------
> tools/perf/util/Build | 1 +
> tools/perf/util/c2c.c | 21 ++++++++
> tools/perf/util/c2c.h | 101 +++++++++++++++++++++++++++++++++++++
> 4 files changed, 126 insertions(+), 102 deletions(-)
> create mode 100644 tools/perf/util/c2c.c
> create mode 100644 tools/perf/util/c2c.h
>
> diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
> index bc16a57e0927..16b00a36fdfc 100644
> --- a/tools/perf/builtin-c2c.c
> +++ b/tools/perf/builtin-c2c.c
> @@ -52,46 +52,10 @@
> #include "ui/progress.h"
> #include "ui/ui.h"
> #include "util/annotate.h"
> +#include "util/c2c.h"
> #include "util/symbol.h"
> #include "util/util.h"
>
> -struct c2c_hists {
> - struct hists hists;
> - struct perf_hpp_list list;
> - struct c2c_stats stats;
> -};
> -
> -struct compute_stats {
> - struct stats lcl_hitm;
> - struct stats rmt_hitm;
> - struct stats lcl_peer;
> - struct stats rmt_peer;
> - struct stats load;
> -};
> -
> -struct c2c_hist_entry {
> - struct c2c_hists *hists;
> - struct evsel *evsel;
> - struct c2c_stats stats;
> - unsigned long *cpuset;
> - unsigned long *nodeset;
> - struct c2c_stats *node_stats;
> - unsigned int cacheline_idx;
> -
> - struct compute_stats cstats;
> -
> - unsigned long paddr;
> - unsigned long paddr_cnt;
> - bool paddr_zero;
> - char *nodestr;
> -
> - /*
> - * must be at the end,
> - * because of its callchain dynamic entry
> - */
> - struct hist_entry he;
> -};
> -
> static char const *coalesce_default = "iaddr";
>
> struct perf_c2c {
> @@ -460,36 +424,6 @@ static const char * const __usage_report[] = {
>
> static const char * const *report_c2c_usage = __usage_report;
>
> -#define C2C_HEADER_MAX 2
> -
> -struct c2c_header {
> - struct {
> - const char *text;
> - int span;
> - } line[C2C_HEADER_MAX];
> -};
> -
> -struct c2c_dimension {
> - struct c2c_header header;
> - const char *name;
> - int width;
> - struct sort_entry *se;
> -
> - int64_t (*cmp)(struct perf_hpp_fmt *fmt,
> - struct hist_entry *, struct hist_entry *);
> - int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> - struct hist_entry *he);
> - int (*color)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> - struct hist_entry *he);
> -};
> -
> -struct c2c_fmt {
> - struct perf_hpp_fmt fmt;
> - struct c2c_dimension *dim;
> -};
> -
> -#define SYMBOL_WIDTH 30
> -
> static struct c2c_dimension dim_symbol;
> static struct c2c_dimension dim_srcline;
>
> @@ -1391,23 +1325,6 @@ cl_idx_empty_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> return scnprintf(hpp->buf, hpp->size, "%*s", width, "");
> }
>
> -#define HEADER_LOW(__h) \
> - { \
> - .line[1] = { \
> - .text = __h, \
> - }, \
> - }
> -
> -#define HEADER_BOTH(__h0, __h1) \
> - { \
> - .line[0] = { \
> - .text = __h0, \
> - }, \
> - .line[1] = { \
> - .text = __h1, \
> - }, \
> - }
> -
> #define HEADER_SPAN(__h0, __h1, __s) \
> { \
> .line[0] = { \
> @@ -1930,22 +1847,6 @@ static struct c2c_dimension *dimensions[] = {
> NULL,
> };
>
> -static void fmt_free(struct perf_hpp_fmt *fmt)
> -{
> - struct c2c_fmt *c2c_fmt;
> -
> - c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
> - free(c2c_fmt);
> -}
> -
> -static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
> -{
> - struct c2c_fmt *c2c_a = container_of(a, struct c2c_fmt, fmt);
> - struct c2c_fmt *c2c_b = container_of(b, struct c2c_fmt, fmt);
> -
> - return c2c_a->dim == c2c_b->dim;
> -}
> -
> static struct c2c_dimension *get_dimension(const char *name)
> {
> unsigned int i;
> @@ -2023,8 +1924,8 @@ static struct c2c_fmt *get_format(const char *name)
> fmt->header = c2c_header;
> fmt->width = c2c_width;
> fmt->collapse = dim->se ? c2c_se_collapse : dim->cmp;
> - fmt->equal = fmt_equal;
> - fmt->free = fmt_free;
> + fmt->equal = c2c_fmt_equal;
> + fmt->free = c2c_fmt_free;
>
> return c2c_fmt;
> }
> diff --git a/tools/perf/util/Build b/tools/perf/util/Build
> index 330311cac550..1dfd92cbe3b7 100644
> --- a/tools/perf/util/Build
> +++ b/tools/perf/util/Build
> @@ -11,6 +11,7 @@ perf-util-y += blake2s.o
> perf-util-y += block-info.o
> perf-util-y += block-range.o
> perf-util-y += build-id.o
> +perf-util-y += c2c.o
> perf-util-y += cacheline.o
> perf-util-$(CONFIG_LIBCAPSTONE) += capstone.o
> perf-util-y += config.o
> diff --git a/tools/perf/util/c2c.c b/tools/perf/util/c2c.c
> new file mode 100644
> index 000000000000..58c7342dff46
> --- /dev/null
> +++ b/tools/perf/util/c2c.c
> @@ -0,0 +1,21 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#include <stdlib.h>
> +#include <linux/kernel.h>
> +#include "hist.h"
> +#include "c2c.h"
> +
> +void c2c_fmt_free(struct perf_hpp_fmt *fmt)
> +{
> + struct c2c_fmt *c2c_fmt;
> +
> + c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
> + free(c2c_fmt);
> +}
> +
> +bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
> +{
> + struct c2c_fmt *c2c_a = container_of(a, struct c2c_fmt, fmt);
> + struct c2c_fmt *c2c_b = container_of(b, struct c2c_fmt, fmt);
> +
> + return c2c_a->dim == c2c_b->dim;
> +}
> diff --git a/tools/perf/util/c2c.h b/tools/perf/util/c2c.h
> new file mode 100644
> index 000000000000..bd0c9d1c9a1a
> --- /dev/null
> +++ b/tools/perf/util/c2c.h
> @@ -0,0 +1,101 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __PERF_UTIL_C2C_H
> +#define __PERF_UTIL_C2C_H
> +
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <linux/types.h>
> +#include "hist.h"
> +#include "mem-events.h"
> +#include "stat.h"
> +
> +struct sort_entry;
> +
> +struct c2c_hists {
> + struct hists hists;
> + struct perf_hpp_list list;
> + struct c2c_stats stats;
> +};
> +
> +struct compute_stats {
> + struct stats lcl_hitm;
> + struct stats rmt_hitm;
> + struct stats lcl_peer;
> + struct stats rmt_peer;
> + struct stats load;
> +};
> +
> +struct c2c_hist_entry {
> + struct c2c_hists *hists;
> + struct evsel *evsel;
> + struct c2c_stats stats;
> + unsigned long *cpuset;
> + unsigned long *nodeset;
> + struct c2c_stats *node_stats;
> + unsigned int cacheline_idx;
> +
> + struct compute_stats cstats;
> +
> + unsigned long paddr;
> + unsigned long paddr_cnt;
> + bool paddr_zero;
> + char *nodestr;
> +
> + /*
> + * must be at the end,
> + * because of its callchain dynamic entry
> + */
> + struct hist_entry he;
> +};
> +
> +#define C2C_HEADER_MAX 2
> +
> +struct c2c_header {
> + struct {
> + const char *text;
> + int span;
> + } line[C2C_HEADER_MAX];
> +};
> +
> +struct c2c_dimension {
> + struct c2c_header header;
> + const char *name;
> + int width;
> + struct sort_entry *se;
> +
> + int64_t (*cmp)(struct perf_hpp_fmt *fmt,
> + struct hist_entry *left, struct hist_entry *right);
> + int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> + struct hist_entry *he);
> + int (*color)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> + struct hist_entry *he);
> +};
> +
> +struct c2c_fmt {
> + struct perf_hpp_fmt fmt;
> + struct c2c_dimension *dim;
> +};
> +
> +#define SYMBOL_WIDTH 30
> +
> +#define HEADER_LOW(__h) \
> + { \
> + .line[1] = { \
> + .text = __h, \
> + }, \
> + }
> +
> +#define HEADER_BOTH(__h0, __h1) \
> + { \
> + .line[0] = { \
> + .text = __h0, \
> + }, \
> + .line[1] = { \
> + .text = __h1, \
> + }, \
> + }
> +
> +void c2c_fmt_free(struct perf_hpp_fmt *fmt);
> +bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b);
> +
> +#endif /* __PERF_UTIL_C2C_H */
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 2/9] perf c2c: add function view model skeleton
2026-08-17 9:46 ` [PATCH v9 2/9] perf c2c: add function view model skeleton Jiebin Sun
2026-08-17 9:48 ` sashiko-bot
@ 2026-08-17 20:53 ` Ian Rogers
2026-08-17 22:48 ` Namhyung Kim
1 sibling, 1 reply; 30+ messages in thread
From: Ian Rogers @ 2026-08-17 20:53 UTC (permalink / raw)
To: Jiebin Sun
Cc: namhyung, acme, mingo, peterz, adrian.hunter, alexander.shishkin,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, linux-perf-users, linux-kernel
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
>
> Add the initial common model for the c2c function view: model state and
> small helpers shared by the hierarchy construction and formatting added
> in later patches.
>
> Build the model from util/ so it remains independent of the TUI and
> command-private symbols.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> Cc: Adrian Hunter <adrian.hunter@intel.com>
> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> Cc: Ian Rogers <irogers@google.com>
> Cc: Ingo Molnar <mingo@redhat.com>
> Cc: James Clark <james.clark@linaro.org>
> Cc: Jiri Olsa <jolsa@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Namhyung Kim <namhyung@kernel.org>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Thomas Falcon <thomas.falcon@intel.com>
> Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
> ---
> tools/perf/util/Build | 1 +
> tools/perf/util/c2c-function.c | 66 ++++++++++++++++++++++++++++++++++
> 2 files changed, 67 insertions(+)
> create mode 100644 tools/perf/util/c2c-function.c
>
> diff --git a/tools/perf/util/Build b/tools/perf/util/Build
> index 1dfd92cbe3b7..b26a0b1ddfa3 100644
> --- a/tools/perf/util/Build
> +++ b/tools/perf/util/Build
> @@ -12,6 +12,7 @@ perf-util-y += block-info.o
> perf-util-y += block-range.o
> perf-util-y += build-id.o
> perf-util-y += c2c.o
> +perf-util-y += c2c-function.o
> perf-util-y += cacheline.o
> perf-util-$(CONFIG_LIBCAPSTONE) += capstone.o
> perf-util-y += config.o
> diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
> new file mode 100644
> index 000000000000..ca82425a28dc
> --- /dev/null
> +++ b/tools/perf/util/c2c-function.c
> @@ -0,0 +1,66 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * C2C function model - function-level cacheline sharing analysis
> + *
> + * Displays a 3-level hierarchy showing which functions share cachelines:
> + * Level 1: Read-side functions sorted by Cycles % (estimated load cycles)
> + * Level 2: Functions sampled writing the shared lines read by level 1
> + * Level 3: The specific cachelines where the two functions contend
> + *
> + * Builds the hierarchy from the existing cacheline histograms
> + * (c2c_hist_entry->hists), reusing the shared c2c data structures.
> + */
> +
> +#include <errno.h>
> +#include <inttypes.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <tools/libc_compat.h> /* reallocarray */
> +#include <linux/list.h>
> +#include <linux/rbtree.h>
> +#include <linux/zalloc.h>
> +
> +#include "addr_location.h"
> +#include "c2c.h"
> +#include "cacheline.h"
> +#include "hist.h"
> +#include "map.h"
> +#include "mem-events.h"
> +#include "mem-info.h"
> +#include "sort.h"
> +#include "symbol.h"
> +#include "thread.h"
nit: the number of #includes is somewhat generous here. I presume
later patches will require these includes. To avoid everything
depending on everything else it would be nice to use forward
declarations when possible. For example, if the only reason for
including the header file was to use a struct's name where it is
passed as a pointer in a function declaration, ie in header files
prefer:
struct map;
int foo(struct map *m);
over
#include "map.h"
int foo(struct map *m);
> +
> +struct c2c_function_model {
> + struct c2c_hists function_hists;
> + /* Total estimated cycles across all level-1 entries. */
> + u64 total_cycles;
> + /* Source cacheline histograms; not owned here. */
> + struct c2c_hists *cl_hists;
> + /* --coalesce field list, used to require iaddr. */
> + const char *cl_sort;
> + /* Do not cap long symbol names. */
> + bool symbol_full;
> +};
> +
> +static struct c2c_function_model c2c_ext __maybe_unused;
> +
> +static inline __maybe_unused u64 c2c_hitm_count(const struct c2c_stats *stats)
> +{
> + return stats->tot_hitm;
> +}
> +
> +static inline __maybe_unused bool symbol_name_equal(struct symbol *a, struct symbol *b)
> +{
> + /* Two unknown symbols compare equal, matching cmp_null() in util/sort.c. */
> + if (!a || !b)
> + return a == b;
> + return arch__compare_symbol_names(a->name, b->name) == 0;
Sashiko rightly flagged this as not being cross-platform compatible,
but this is a pre-existing issue that looks relatively easy to clean
up but only really impacts PowerPC and so is hard for me to test. I'll
try to do it anyway.
Thanks,
Ian
> +}
> +
> +static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
> +{
> + if (he->mem_info)
> + return mem_info__iaddr(he->mem_info)->addr;
> + return he->ip;
> +}
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 3/9] perf c2c: add column rendering for function view
2026-08-17 9:46 ` [PATCH v9 3/9] perf c2c: add column rendering for function view Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
@ 2026-08-17 20:58 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: Ian Rogers @ 2026-08-17 20:58 UTC (permalink / raw)
To: Jiebin Sun
Cc: namhyung, acme, mingo, peterz, adrian.hunter, alexander.shishkin,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, linux-perf-users, linux-kernel
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
>
> Add renderers for the function view's Cycles %, Store count, and
> hierarchy identity columns. The identity column renders the read-side
> function, contending writer, or cacheline, with indentation for the
> hierarchy level. Also add width and header helpers, estimated-cycle
> calculation, comparators, and the dimension table that ties them together.
>
> Clamp the identity renderer's returned length to its local buffer before
> using it for pointer and padding calculations. This handles snprintf-style
> would-have-been lengths without changing normal output.
>
> The next patch connects these dimensions to the view's HPP lists, so the
> symbols used only there are temporarily marked __maybe_unused.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> Cc: Adrian Hunter <adrian.hunter@intel.com>
> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> Cc: Ian Rogers <irogers@google.com>
> Cc: Ingo Molnar <mingo@redhat.com>
> Cc: James Clark <james.clark@linaro.org>
> Cc: Jiri Olsa <jolsa@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Namhyung Kim <namhyung@kernel.org>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Thomas Falcon <thomas.falcon@intel.com>
> Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
Reviewed-by: Ian Rogers <irogers@google.com>
> ---
> tools/perf/util/c2c-function.c | 353 +++++++++++++++++++++++++++++++++
> 1 file changed, 353 insertions(+)
>
> diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
> index ca82425a28dc..7fce415c0f07 100644
> --- a/tools/perf/util/c2c-function.c
> +++ b/tools/perf/util/c2c-function.c
> @@ -64,3 +64,356 @@ static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
> return mem_info__iaddr(he->mem_info)->addr;
> return he->ip;
> }
> +
> +/*
> + * Hierarchy levels (by depth): L1 = read-side function, L2 = the writing
> + * function it contends with, L3 = the specific shared cacheline.
> + */
> +static inline bool hist_entry__is_cacheline(struct hist_entry *he)
nit: I think the inline here is to make things compile/link in the
partial state. In general it shouldn't be necessary to have "inline"
in a .c file.
Thanks,
Ian
> +{
> + return he->parent_he && he->parent_he->parent_he; /* level 3: cacheline */
> +}
> +
> +/* Spaces of indent per hierarchy level, like the normal report view. */
> +#define C2C_FUNC_INDENT 2
> +
> +/* Width of the folded-sign prefix ("%c ") each identity cell emits. */
> +#define C2C_FUNC_FOLD_WIDTH 2
> +
> +/*
> + * Write he->depth levels of leading indentation into @buf, so lower-level
> + * entries are visually nested under their parent. Returns bytes written.
> + */
> +static int hist_entry__indent(struct hist_entry *he, char *buf, size_t size)
> +{
> + int indent = he->depth * C2C_FUNC_INDENT;
> +
> + if (indent <= 0 || (size_t)indent >= size)
> + return 0;
> +
> + return scnprintf(buf, size, "%*s", indent, "");
> +}
> +
> +static int symbol_width(struct hists *hists, struct sort_entry *se)
> +{
> + int width = hists__col_len(hists, se->se_width_idx);
> +
> + /*
> + * Cap long symbol names as the cacheline view does. The stored column
> + * length is grown up front to fit the deepest, longest identity cell
> + * (including a level-3 cacheline address), so this cap never shrinks the
> + * column below what the cacheline address needs.
> + */
> + if (!c2c_ext.symbol_full && width > SYMBOL_WIDTH)
> + width = SYMBOL_WIDTH;
> +
> + return width;
> +}
> +
> +static struct c2c_dimension dim_symbol_view;
> +
> +/*
> + * c2c_width - Calculate width for a C2C column in function view
> + */
> +static int c2c_width(struct perf_hpp_fmt *fmt,
> + struct perf_hpp *hpp __maybe_unused,
> + struct hists *hists)
> +{
> + struct c2c_fmt *c2c_fmt;
> + struct c2c_dimension *dim;
> +
> + c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
> + dim = c2c_fmt->dim;
> +
> + if (dim == &dim_symbol_view)
> + return symbol_width(hists, dim->se);
> +
> + return dim->se ? hists__col_len(hists, dim->se->se_width_idx) :
> + dim->width;
> +}
> +
> +static int __maybe_unused c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> + struct hists *hists, int line, int *span)
> +{
> + struct c2c_fmt *c2c_fmt;
> + struct c2c_dimension *dim;
> + const char *text = NULL;
> + int width = c2c_width(fmt, hpp, hists);
> +
> + c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
> + dim = c2c_fmt->dim;
> +
> + if (dim->se) {
> + text = dim->header.line[line].text;
> + /* Use the last line from sort_entry if not defined. */
> + if (!text && line == hists->hpp_list->nr_header_lines - 1)
> + text = dim->se->se_header;
> + } else {
> + text = dim->header.line[line].text;
> +
> + if (span) {
> + if (*span) {
> + (*span)--;
> + return 0;
> + }
> +
> + *span = dim->header.line[line].span;
> + }
> + }
> +
> + if (!text)
> + text = "";
> +
> + return scnprintf(hpp->buf, hpp->size, "%*s", width, text);
> +}
> +
> +/*
> + * Return the estimated total cycles for a c2c_hist_entry
> + * (rmt_hitm + lcl_hitm + rmt_peer + lcl_peer + other loads).
> + */
> +static u64 c2c_hist_entry__cycles(struct c2c_hist_entry *c2c_he)
> +{
> + struct compute_stats *cs = &c2c_he->cstats;
> + double cycles = 0;
> +
> + /*
> + * compute_stats() in builtin-c2c.c routes each load sample into exactly
> + * one cstats bucket (rmt_hitm, lcl_hitm, rmt_peer, lcl_peer or plain
> + * load), so each bucket's cycle total is its mean times its own sample
> + * count. Summing the per-bucket totals avoids both dropping peer-snoop
> + * cycles and double counting a sample that carries several data-source
> + * flags (e.g. Arm SPE sets HITM and PEER on the same load), which would
> + * happen if the mean were multiplied by the non-exclusive stats counts.
> + */
> + cycles += avg_stats(&cs->rmt_hitm) * cs->rmt_hitm.n;
> + cycles += avg_stats(&cs->lcl_hitm) * cs->lcl_hitm.n;
> + cycles += avg_stats(&cs->rmt_peer) * cs->rmt_peer.n;
> + cycles += avg_stats(&cs->lcl_peer) * cs->lcl_peer.n;
> + cycles += avg_stats(&cs->load) * cs->load.n;
> +
> + return (u64)cycles;
> +}
> +
> +/* Sum c2c_hist_entry__cycles() across all level-1 entries. */
> +static u64 __maybe_unused c2c_ext__total_cycles(void)
> +{
> + struct rb_node *nd;
> + u64 total = 0;
> +
> + for (nd = rb_first_cached(&c2c_ext.function_hists.hists.entries); nd;
> + nd = rb_next(nd)) {
> + struct c2c_hist_entry *c2c_he =
> + rb_entry(nd, struct c2c_hist_entry, he.rb_node);
> +
> + total += c2c_hist_entry__cycles(c2c_he);
> + }
> + return total;
> +}
> +
> +/*
> + * Store count shown in the column: a level-3 cacheline leaf shows its parent
> + * level-2 writer's stores on that line, not all stores on the line. A level-2
> + * writer shows the sum across its level-3 cachelines. A level-1 reader shows
> + * the sum across all included writers on the cachelines it reads; this is not
> + * the reader function's own store count and is not additive across readers.
> + */
> +static u64 hist_entry__displayed_stores(struct hist_entry *he)
> +{
> + struct c2c_hist_entry *c2c_he = container_of(he, struct c2c_hist_entry, he);
> + struct rb_node *nd;
> + u64 stores = 0;
> +
> + /* Level-2/3 entries already aggregate the stores they represent. */
> + if (he->parent_he)
> + return c2c_he->stats.store;
> +
> + for (nd = rb_first_cached(&he->hroot_out); nd; nd = rb_next(nd)) {
> + struct c2c_hist_entry *child_c2c =
> + rb_entry(nd, struct c2c_hist_entry, he.rb_node);
> + stores += child_c2c->stats.store;
> + }
> + return stores;
> +}
> +
> +static int
> +total_stores_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> + struct hist_entry *he)
> +{
> + int width = c2c_width(fmt, hpp, he->hists);
> + u64 total = hist_entry__displayed_stores(he);
> +
> + return scnprintf(hpp->buf, hpp->size, "%*" PRIu64, width, total);
> +}
> +
> +/*
> + * symbol_view_entry - Render the unified, indented identity column.
> + *
> + * All three levels share this single column so the hierarchy reads top-down
> + * with progressive indentation, like the normal report hierarchy view. It is
> + * a function-centric view with no dedicated code-address column. Verbose
> + * function rows can still include a representative address:
> + * L1 read-side function: "- [k] cpupri_set"
> + * L2 writing function: " - [k] pull_rt_task"
> + * L3 shared cacheline: " 0xff2d0082809da080"
> + */
> +static int
> +symbol_view_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> + struct hist_entry *he)
> +{
> + int width = c2c_width(fmt, hpp, he->hists);
> + int text_width;
> + int ret;
> + char folded_sign;
> +
> + ret = hist_entry__indent(he, hpp->buf, hpp->size);
> +
> + folded_sign = he->has_children ? (he->unfolded ? '-' : '+') : ' ';
> + ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%c ", folded_sign);
> +
> + text_width = width - ret;
> + if (text_width <= 0)
> + return ret;
> +
> + if (hist_entry__is_cacheline(he)) {
> + /* Level 3: the shared cacheline address. */
> + u64 addr = he->mem_info ?
> + cl_address(mem_info__daddr(he->mem_info)->addr, chk_double_cl) : 0;
> + char symbuf[32];
> +
> + scnprintf(symbuf, sizeof(symbuf), "0x%" PRIx64, addr);
> + ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%-*.*s",
> + text_width, text_width, symbuf);
> + } else {
> + /* Level 1 and level 2 are both functions. */
> + size_t cell_size;
> + int len;
> +
> + if ((size_t)ret >= hpp->size)
> + return ret;
> +
> + cell_size = min_t(size_t, hpp->size - ret,
> + (size_t)text_width + 1);
> + len = sort_sym.se_snprintf(he, hpp->buf + ret, cell_size,
> + text_width);
> + /*
> + * se_snprintf() accumulates repsep_snprintf() calls, which cap
> + * their return at the remaining size - 1 rather than reporting
> + * what the format would have needed, so len stays below
> + * cell_size. Clamp anyway so ret cannot leave hpp->buf.
> + */
> + if (len < 0)
> + len = 0;
> + else
> + len = min_t(size_t, len, cell_size - 1);
> +
> + ret += len;
> + if (len < text_width)
> + ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%*s",
> + text_width - len, "");
> + }
> +
> + return ret;
> +}
> +
> +/*
> + * cycles_percent_entry - Render cycles percentage column
> + */
> +static int
> +cycles_percent_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> + struct hist_entry *he)
> +{
> + struct c2c_hist_entry *c2c_he;
> + int width = c2c_width(fmt, hpp, he->hists);
> + u64 fn_cycles, total_cycles;
> + char folded_sign;
> + double pct;
> + int ret, pct_width;
> +
> + /* Hide Cycles Percent for child functions and cachelines. */
> + if (he->parent_he)
> + return scnprintf(hpp->buf, hpp->size, "%*s", width, "");
> +
> + c2c_he = container_of(he, struct c2c_hist_entry, he);
> + fn_cycles = c2c_hist_entry__cycles(c2c_he);
> + /* Populated by build_function_view_hierarchy() once the L1 tree is built. */
> + total_cycles = c2c_ext.total_cycles;
> + pct = total_cycles > 0 ? (double)fn_cycles / total_cycles * 100.0 : 0.0;
> +
> + /* Add folded sign only for level-1 entries */
> + folded_sign = he->has_children ? (he->unfolded ? '-' : '+') : ' ';
> + ret = scnprintf(hpp->buf, hpp->size, "%c ", folded_sign);
> +
> + pct_width = width - ret;
> + if (pct_width <= 0)
> + return ret;
> + ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%*.2f%%", pct_width - 1, pct);
> + return ret;
> +}
> +
> +/*
> + * cycles_percent_cmp - Comparison function for cycles percentage sorting
> + */
> +static int64_t
> +cycles_percent_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
> + struct hist_entry *left, struct hist_entry *right)
> +{
> + struct c2c_hist_entry *c2c_left = container_of(left, struct c2c_hist_entry, he);
> + struct c2c_hist_entry *c2c_right = container_of(right, struct c2c_hist_entry, he);
> + u64 cycles_left, cycles_right;
> +
> + /* Cycles Percent is only shown for level-1 entries; others compare equal. */
> + if (left->parent_he || right->parent_he)
> + return 0;
> +
> + cycles_left = c2c_hist_entry__cycles(c2c_left);
> + cycles_right = c2c_hist_entry__cycles(c2c_right);
> +
> + return (cycles_left > cycles_right) - (cycles_left < cycles_right);
> +}
> +
> +/*
> + * total_stores_cmp - Comparison function for total stores sorting
> + */
> +static int64_t
> +total_stores_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
> + struct hist_entry *left, struct hist_entry *right)
> +{
> + u64 left_store = hist_entry__displayed_stores(left);
> + u64 right_store = hist_entry__displayed_stores(right);
> +
> + return (left_store > right_store) - (left_store < right_store);
> +}
> +
> +/*
> + * Function view dimensions
> + */
> +static struct c2c_dimension dim_cycles_percent = {
> + .header = HEADER_BOTH("Cycles", "%"),
> + .name = "cycles_percent",
> + .cmp = cycles_percent_cmp,
> + .entry = cycles_percent_entry,
> + .width = 9,
> +};
> +
> +static struct c2c_dimension dim_total_stores = {
> + .header = HEADER_BOTH("Store", "count"),
> + .name = "total_stores",
> + .cmp = total_stores_cmp,
> + .entry = total_stores_entry,
> + .width = 7,
> +};
> +
> +static struct c2c_dimension dim_symbol_view = {
> + .header = HEADER_LOW("Function / Contending function / Cacheline"),
> + .name = "symbol_view",
> + .se = &sort_sym,
> + .entry = symbol_view_entry,
> + .width = SYMBOL_WIDTH,
> +};
> +
> +static struct c2c_dimension *function_view_dimensions[] __maybe_unused = {
> + &dim_cycles_percent,
> + &dim_total_stores,
> + &dim_symbol_view,
> + NULL,
> +};
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 4/9] perf c2c: add HPP list parsing for function view columns
2026-08-17 9:46 ` [PATCH v9 4/9] perf c2c: add HPP list parsing for function view columns Jiebin Sun
2026-08-17 9:50 ` sashiko-bot
@ 2026-08-17 21:04 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: Ian Rogers @ 2026-08-17 21:04 UTC (permalink / raw)
To: Jiebin Sun
Cc: namhyung, acme, mingo, peterz, adrian.hunter, alexander.shishkin,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, linux-perf-users, linux-kernel
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
>
> Add the parser that builds the function view's local HPP output and
> sort lists from field strings. This includes dimension lookup, comparator
> wrappers, c2c_fmt allocation, and the initialization entry points used by
> the hierarchy builder.
>
> The generic perf_hpp__setup_output_field() registers formats on the global
> perf_hpp_list. Using it here would leave the function view's local list
> without output columns and modify the cacheline view's list instead. Add
> c2c_function_hists__setup_output_field() to append sort keys to the local
> output list.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> Cc: Adrian Hunter <adrian.hunter@intel.com>
> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> Cc: Ian Rogers <irogers@google.com>
> Cc: Ingo Molnar <mingo@redhat.com>
> Cc: James Clark <james.clark@linaro.org>
> Cc: Jiri Olsa <jolsa@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Namhyung Kim <namhyung@kernel.org>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Thomas Falcon <thomas.falcon@intel.com>
> Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/util/c2c-function.c | 269 ++++++++++++++++++++++++++++++++-
> 1 file changed, 266 insertions(+), 3 deletions(-)
>
> diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
> index 7fce415c0f07..d27544016660 100644
> --- a/tools/perf/util/c2c-function.c
> +++ b/tools/perf/util/c2c-function.c
> @@ -23,6 +23,7 @@
> #include "addr_location.h"
> #include "c2c.h"
> #include "cacheline.h"
> +#include "debug.h"
> #include "hist.h"
> #include "map.h"
> #include "mem-events.h"
> @@ -132,8 +133,8 @@ static int c2c_width(struct perf_hpp_fmt *fmt,
> dim->width;
> }
>
> -static int __maybe_unused c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> - struct hists *hists, int line, int *span)
> +static int c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
> + struct hists *hists, int line, int *span)
> {
> struct c2c_fmt *c2c_fmt;
> struct c2c_dimension *dim;
> @@ -411,9 +412,271 @@ static struct c2c_dimension dim_symbol_view = {
> .width = SYMBOL_WIDTH,
> };
>
> -static struct c2c_dimension *function_view_dimensions[] __maybe_unused = {
> +static struct c2c_dimension *function_view_dimensions[] = {
> &dim_cycles_percent,
> &dim_total_stores,
> &dim_symbol_view,
> NULL,
> };
> +
> +static struct c2c_dimension *get_function_dimension(const char *name)
> +{
> + unsigned int i;
> +
> + for (i = 0; function_view_dimensions[i]; i++) {
> + struct c2c_dimension *dim = function_view_dimensions[i];
> +
> + if (!strcmp(dim->name, name))
> + return dim;
> + }
> +
> + return NULL;
> +}
> +
> +/* Wrappers so sort_entry-backed dimensions sort/collapse via their se. */
> +static int64_t c2c_se_cmp(struct perf_hpp_fmt *fmt,
> + struct hist_entry *a, struct hist_entry *b)
> +{
> + struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
> + struct c2c_dimension *dim = c2c_fmt->dim;
> +
> + return dim->se->se_cmp(a, b);
> +}
> +
> +static int64_t c2c_se_collapse(struct perf_hpp_fmt *fmt,
> + struct hist_entry *a, struct hist_entry *b)
> +{
> + struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
> + struct c2c_dimension *dim = c2c_fmt->dim;
> + int64_t (*collapse_fn)(struct hist_entry *a, struct hist_entry *b);
> +
> + collapse_fn = dim->se->se_collapse ?: dim->se->se_cmp;
> + return collapse_fn(a, b);
> +}
> +
> +static int64_t c2c_se_sort(struct perf_hpp_fmt *fmt,
> + struct hist_entry *a, struct hist_entry *b)
> +{
> + struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
> + struct c2c_dimension *dim = c2c_fmt->dim;
> + int64_t (*sort_fn)(struct hist_entry *a, struct hist_entry *b);
> +
> + sort_fn = dim->se->se_sort ?: dim->se->se_cmp;
> + return sort_fn(a, b);
> +}
> +
> +/*
> + * Build the c2c_fmt for @name. Returns:
> + * 0 and *fmtp set on success;
> + * -ENOENT and *fmtp = NULL if @name is not a function-view dimension;
> + * -ENOMEM if allocation failed (distinct from -ENOENT so
> + * the caller does not misreport it as an
> + * "invalid field").
> + */
> +static int get_function_format(const char *name, struct c2c_fmt **fmtp)
> +{
> + struct c2c_dimension *dim = get_function_dimension(name);
> + struct c2c_fmt *c2c_fmt;
> + struct perf_hpp_fmt *fmt;
> +
> + *fmtp = NULL;
> +
> + if (!dim)
> + return -ENOENT;
> +
> + c2c_fmt = zalloc(sizeof(*c2c_fmt));
> + if (!c2c_fmt)
> + return -ENOMEM;
> +
> + fmt = &c2c_fmt->fmt;
> +
> + c2c_fmt->dim = dim;
> + INIT_LIST_HEAD(&fmt->list);
> + INIT_LIST_HEAD(&fmt->sort_list);
> +
> + fmt->cmp = dim->se ? c2c_se_cmp : dim->cmp;
> + fmt->sort = dim->se ? c2c_se_sort : dim->cmp;
> + fmt->color = dim->color;
> + fmt->entry = dim->entry;
> + fmt->header = c2c_header;
> + fmt->width = c2c_width;
> + fmt->collapse = dim->se ? c2c_se_collapse : dim->cmp;
> + fmt->equal = c2c_fmt_equal;
> + fmt->free = c2c_fmt_free;
> +
> + *fmtp = c2c_fmt;
> + return 0;
> +}
> +
> +static int
> +c2c_function_hists__init_output(struct perf_hpp_list *hpp_list, char *name,
> + struct perf_env *env __maybe_unused)
> +{
> + struct c2c_fmt *c2c_fmt;
> + int ret;
> +
> + ret = get_function_format(name, &c2c_fmt);
> + if (ret == -ENOMEM)
> + return ret;
> + /* The function view only accepts its own dimensions. */
> + if (ret == -ENOENT)
> + return -EINVAL;
> +
> + /*
> + * Mark symbol-backed columns so hists__has(hists, sym) is correct.
> + * Only dim_symbol_view carries a sort_entry (.se); the function
> + * view's field strings are fixed and always include symbol_view, so
> + * this single check is sufficient (unlike the user-configurable
> + * cacheline view, which must also test dim_iaddr).
> + */
> + if (c2c_fmt->dim->se == &sort_sym)
> + hpp_list->sym = 1;
> +
> + perf_hpp_list__column_register(hpp_list, &c2c_fmt->fmt);
> + return 0;
> +}
> +
> +static int
> +c2c_function_hists__init_sort(struct perf_hpp_list *hpp_list, char *name,
> + struct perf_env *env __maybe_unused)
> +{
> + struct c2c_fmt *c2c_fmt;
> + int ret;
> +
> + ret = get_function_format(name, &c2c_fmt);
> + if (ret == -ENOMEM)
> + return ret;
> + /* The function view only accepts its own dimensions. */
> + if (ret == -ENOENT)
> + return -EINVAL;
> +
> + /* Mark symbol-backed sort keys so hists__has(hists, sym) is correct. */
> + if (c2c_fmt->dim->se == &sort_sym)
> + hpp_list->sym = 1;
> +
> + perf_hpp_list__register_sort_field(hpp_list, &c2c_fmt->fmt);
> + return 0;
> +}
> +
> +typedef int (*hpp_list_add_fn)(struct perf_hpp_list *hpp_list, char *name,
> + struct perf_env *env);
> +
> +static int function_hpp_list__add_tokens(struct perf_hpp_list *hpp_list, char *list,
> + struct perf_env *env, hpp_list_add_fn add)
> +{
> + char *tok, *tmp;
> + int ret;
> +
> + if (!list)
> + return 0;
> +
> + for (tok = strtok_r(list, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) {
> + ret = add(hpp_list, tok, env);
> + if (ret) {
> + if (ret == -EINVAL || ret == -ESRCH)
> + pr_err("Invalid c2c function-view field: %s\n", tok);
> + return ret;
> + }
> + }
> + return 0;
> +}
> +
> +/*
> + * Append the function view's sort keys to its own output fields, mirroring
> + * perf_hpp__setup_output_field() but on the local @list. The shared helper
> + * registers onto the global perf_hpp_list, which would leave this local list
> + * without output columns, so the function view keeps its own copy here.
> + */
> +static void c2c_function_hists__setup_output_field(struct perf_hpp_list *list)
> +{
> + struct perf_hpp_fmt *fmt;
> +
> + perf_hpp_list__for_each_sort_list(list, fmt) {
> + struct perf_hpp_fmt *pos;
> +
> + if (!fmt->entry && !fmt->color)
> + continue;
> +
> + perf_hpp_list__for_each_format(list, pos) {
> + if (c2c_fmt_equal(fmt, pos))
> + goto next;
> + }
> +
> + perf_hpp_list__column_register(list, fmt);
> +next:
> + continue;
> + }
> +}
> +
> +static int
> +function_hpp_list__parse(struct perf_hpp_list *hpp_list,
> + const char *output_str,
> + const char *sort_str,
> + struct perf_env *env)
> +{
> + char *output = output_str ? strdup(output_str) : NULL;
> + char *sort = sort_str ? strdup(sort_str) : NULL;
> + int ret = 0;
> +
> + if ((output_str && !output) || (sort_str && !sort)) {
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + ret = function_hpp_list__add_tokens(hpp_list, output, env,
> + c2c_function_hists__init_output);
> + if (ret)
> + goto out;
> +
> + ret = function_hpp_list__add_tokens(hpp_list, sort, env,
> + c2c_function_hists__init_sort);
> + if (ret)
> + goto out;
> +
> + c2c_function_hists__setup_output_field(hpp_list);
> +out:
> + if (ret)
> + perf_hpp__reset_output_field(hpp_list);
> + free(output);
> + free(sort);
> + return ret;
> +}
> +
> +static int __maybe_unused
> +c2c_function_hists__init(struct c2c_hists *hists,
> + const char *sort,
> + int nr_header_lines,
> + struct perf_env *env)
> +{
> + __hists__init(&hists->hists, &hists->list);
> +
> + perf_hpp_list__init(&hists->list);
> +
> + hists->list.nr_header_lines = nr_header_lines;
> +
> + return function_hpp_list__parse(&hists->list, /*output=*/NULL, sort, env);
> +}
> +
> +static int __maybe_unused
> +c2c_function_hists__reinit(struct c2c_hists *c2c_hists,
> + const char *output,
> + const char *sort,
> + struct perf_env *env)
> +{
> + int nr_header_lines = c2c_hists->list.nr_header_lines;
> +
> + perf_hpp__reset_output_field(&c2c_hists->list);
> +
> + /* Clear stale state flags so a different output/sort set starts fresh. */
> + c2c_hists->list.need_collapse = 0;
> + c2c_hists->list.parent = 0;
> + c2c_hists->list.sym = 0;
> + c2c_hists->list.dso = 0;
> + c2c_hists->list.socket = 0;
> + c2c_hists->list.thread = 0;
> + c2c_hists->list.comm = 0;
> + c2c_hists->list.comm_nodigit = 0;
> + c2c_hists->list.nr_header_lines = nr_header_lines;
> +
> + return function_hpp_list__parse(&c2c_hists->list, output, sort, env);
> +}
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 5/9] perf c2c: add function view stats merge and memory management
2026-08-17 9:46 ` [PATCH v9 5/9] perf c2c: add function view stats merge and memory management Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
@ 2026-08-17 21:05 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: Ian Rogers @ 2026-08-17 21:05 UTC (permalink / raw)
To: Jiebin Sun
Cc: namhyung, acme, mingo, peterz, adrian.hunter, alexander.shishkin,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, linux-perf-users, linux-kernel
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
>
> Add the per-entry stats/cstats aggregation helpers and hierarchy teardown.
> Child common fields are released through hist_entry__delete(), while the
> function-view free callback handles the private child tree and containing
> allocation. Also add a helper for pruning writer entries with no stores or
> cacheline children.
>
> These are used by the entry-creation and builder patches that follow and
> are __maybe_unused until then.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> Cc: Adrian Hunter <adrian.hunter@intel.com>
> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> Cc: Ian Rogers <irogers@google.com>
> Cc: Ingo Molnar <mingo@redhat.com>
> Cc: James Clark <james.clark@linaro.org>
> Cc: Jiri Olsa <jolsa@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Namhyung Kim <namhyung@kernel.org>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Thomas Falcon <thomas.falcon@intel.com>
> Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/util/c2c-function.c | 163 +++++++++++++++++++++++++++++++++
> 1 file changed, 163 insertions(+)
>
> diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
> index d27544016660..aa6be7c72977 100644
> --- a/tools/perf/util/c2c-function.c
> +++ b/tools/perf/util/c2c-function.c
> @@ -680,3 +680,166 @@ c2c_function_hists__reinit(struct c2c_hists *c2c_hists,
>
> return function_hpp_list__parse(&c2c_hists->list, output, sort, env);
> }
> +
> +/* Welford online merge of two "stats" (from util/stat.h) accumulators. */
> +static void c2c_stats_merge(struct stats *dest, const struct stats *src)
> +{
> + double delta;
> +
> + if (src->n == 0)
> + return;
> +
> + if (dest->n == 0) {
> + *dest = *src;
> + return;
> + }
> +
> + delta = src->mean - dest->mean;
> + dest->M2 += src->M2 + delta * delta * dest->n * src->n / (dest->n + src->n);
> + dest->mean = (dest->mean * dest->n + src->mean * src->n) / (dest->n + src->n);
> + dest->n += src->n;
> +
> + /* Update min/max */
> + if (src->max > dest->max)
> + dest->max = src->max;
> + if (src->min < dest->min)
> + dest->min = src->min;
> +}
> +
> +/* Merge compute_stats during function aggregation. */
> +static void __maybe_unused c2c_add_cstats(struct compute_stats *dest,
> + const struct compute_stats *src)
> +{
> + c2c_stats_merge(&dest->rmt_hitm, &src->rmt_hitm);
> + c2c_stats_merge(&dest->lcl_hitm, &src->lcl_hitm);
> + c2c_stats_merge(&dest->rmt_peer, &src->rmt_peer);
> + c2c_stats_merge(&dest->lcl_peer, &src->lcl_peer);
> + c2c_stats_merge(&dest->load, &src->load);
> +}
> +
> +static bool __maybe_unused hist_entry__add_c2c_stats(struct hist_entry *he,
> + const struct c2c_stats *stats)
> +{
> + u64 nr_events = c2c_hitm_count(stats) + stats->rmt_peer + stats->lcl_peer;
> + u64 weight1 = c2c_hitm_count(stats);
> +
> + /*
> + * Allocate before touching he->stat, so a failure here leaves the
> + * entry unmodified and the caller can bail out without having
> + * half-updated the statistics.
> + */
> + if (symbol_conf.cumulate_callchain && !he->stat_acc) {
> + he->stat_acc = calloc(1, sizeof(struct he_stat));
> + if (!he->stat_acc)
> + return false;
> + }
> +
> + he->stat.nr_events += nr_events;
> + he->stat.period += nr_events;
> + he->stat.weight1 += weight1;
> +
> + if (!symbol_conf.cumulate_callchain)
> + return true;
> +
> + he->stat_acc->nr_events += nr_events;
> + he->stat_acc->period += nr_events;
> + he->stat_acc->weight1 += weight1;
> +
> + return true;
> +}
> +
> +static void c2c_he__free_hierarchy(struct hist_entry *he);
> +
> +/*
> + * Free a function-view histogram entry (hist_entry_ops::free).
> + */
> +static void __maybe_unused c2c_function_he_free(void *ptr)
> +{
> + struct hist_entry *he = ptr;
> + struct c2c_hist_entry *c2c_he;
> +
> + c2c_he = container_of(he, struct c2c_hist_entry, he);
> +
> + if (c2c_he->hists) {
> + perf_hpp__reset_output_field(&c2c_he->hists->list);
> + hists__delete_all_entries(&c2c_he->hists->hists);
> + zfree(&c2c_he->hists);
> + }
> +
> + c2c_he__free_hierarchy(he);
> +
> + zfree(&c2c_he->nodeset);
> + zfree(&c2c_he->cpuset);
> + zfree(&c2c_he->nodestr);
> + zfree(&c2c_he->node_stats);
> +
> + free(c2c_he);
> +}
> +
> +static void c2c_he__free_hierarchy(struct hist_entry *he)
> +{
> + struct rb_node *nd;
> + struct hist_entry *child_he;
> +
> + /*
> + * A leaf entry stores its callchains in the sorted_chain member, which
> + * shares a union with the hroot_in/hroot_out child trees, so its
> + * hroot_out is not a valid subtree to walk. Leaf entries never have a
> + * child hierarchy here, so stop before touching hroot_out.
> + */
> + if (he->leaf)
> + return;
> +
> + if (RB_EMPTY_ROOT(&he->hroot_out.rb_root))
> + return;
> +
> + nd = rb_first_cached(&he->hroot_out);
> + while (nd) {
> + struct rb_node *next = rb_next(nd);
> +
> + child_he = rb_entry(nd, struct hist_entry, rb_node);
> + rb_erase_cached(&child_he->rb_node, &he->hroot_out);
> + hist_entry__delete(child_he);
> +
> + nd = next;
> + }
> +
> + /* All children erased; clear the tree (and its cached leftmost). */
> + he->hroot_out = RB_ROOT_CACHED;
> +}
> +
> +/*
> + * Drop level-2 writing functions that carry no stores or
> + * no cacheline children. Writers are only added when they store into a shared
> + * line, so this is mainly a safety net. Returns the number of surviving
> + * writers.
> + */
> +static int __maybe_unused c2c_he__prune_empty_writers(struct hist_entry *l1_he)
> +{
> + struct rb_node *nd;
> + int surviving = 0;
> +
> + if (!l1_he->has_children)
> + return 0;
> +
> + nd = rb_first_cached(&l1_he->hroot_out);
> + while (nd) {
> + struct rb_node *next = rb_next(nd);
> + struct hist_entry *l2_he = rb_entry(nd, struct hist_entry, rb_node);
> +
> + if (l2_he->has_children && hist_entry__displayed_stores(l2_he) > 0) {
> + surviving++;
> + } else {
> + rb_erase_cached(&l2_he->rb_node, &l1_he->hroot_out);
> + hist_entry__delete(l2_he);
> + }
> + nd = next;
> + }
> +
> + if (!surviving) {
> + l1_he->hroot_out = RB_ROOT_CACHED;
> + l1_he->has_children = false;
> + l1_he->unfolded = false;
> + }
> + return surviving;
> +}
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 6/9] perf c2c: add function view hierarchy entry creation
2026-08-17 9:46 ` [PATCH v9 6/9] perf c2c: add function view hierarchy entry creation Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
@ 2026-08-17 21:06 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: Ian Rogers @ 2026-08-17 21:06 UTC (permalink / raw)
To: Jiebin Sun
Cc: namhyung, acme, mingo, peterz, adrian.hunter, alexander.shishkin,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, linux-perf-users, linux-kernel
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
>
> Add the entry-creation layer: owned-reference child allocation and
> insertion, and the level-1/2/3 lookup-or-create functions keyed by
> function symbol (level 1 read-side, level 2 writer) and by the source
> cacheline's existing index (level 3).
>
> Give synthetic children normal entry operations and acquire their thread
> and map-symbol references. This lets the hierarchy teardown use
> hist_entry__delete() for the common fields while the function-view free
> callback handles the private child tree and containing allocation.
>
> Reuse cacheline_idx to preserve the source entry identity without adding
> function-view-only state. Add c2c_function__find_cacheline() to locate the
> original cacheline entry by the same index.
>
> These are driven by the hierarchy builder in the next patch and are
> __maybe_unused until then.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> Cc: Adrian Hunter <adrian.hunter@intel.com>
> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> Cc: Ian Rogers <irogers@google.com>
> Cc: Ingo Molnar <mingo@redhat.com>
> Cc: James Clark <james.clark@linaro.org>
> Cc: Jiri Olsa <jolsa@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Namhyung Kim <namhyung@kernel.org>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Thomas Falcon <thomas.falcon@intel.com>
> Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/util/c2c-function.c | 319 +++++++++++++++++++++++++++++++--
> tools/perf/util/c2c.h | 2 +
> 2 files changed, 310 insertions(+), 11 deletions(-)
>
> diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
> index aa6be7c72977..c005fb5454a7 100644
> --- a/tools/perf/util/c2c-function.c
> +++ b/tools/perf/util/c2c-function.c
> @@ -24,6 +24,7 @@
> #include "c2c.h"
> #include "cacheline.h"
> #include "debug.h"
> +#include "dso.h"
> #include "hist.h"
> #include "map.h"
> #include "mem-events.h"
> @@ -51,12 +52,34 @@ static inline __maybe_unused u64 c2c_hitm_count(const struct c2c_stats *stats)
> return stats->tot_hitm;
> }
>
> -static inline __maybe_unused bool symbol_name_equal(struct symbol *a, struct symbol *b)
> +static int64_t c2c_function_cmp(const struct map_symbol *left,
> + const struct map_symbol *right)
> {
> - /* Two unknown symbols compare equal, matching cmp_null() in util/sort.c. */
> - if (!a || !b)
> - return a == b;
> - return arch__compare_symbol_names(a->name, b->name) == 0;
> + const struct dso *left_dso = left->map ? map__dso(left->map) : NULL;
> + const struct dso *right_dso = right->map ? map__dso(right->map) : NULL;
> + int ret;
> +
> + if (!left_dso || !right_dso) {
> + if (left_dso != right_dso)
> + return left_dso ? 1 : -1;
> + } else {
> + /*
> + * Use the same DSO name as _sort__dso_cmp() (short name unless
> + * verbose), so this matches the DSO comparison the level-1
> + * entries are deduplicated by; otherwise same-basename DSOs
> + * could be split or merged inconsistently across levels.
> + */
> + const char *left_name = verbose > 0 ?
> + dso__long_name(left_dso) : dso__short_name(left_dso);
> + const char *right_name = verbose > 0 ?
> + dso__long_name(right_dso) : dso__short_name(right_dso);
> +
> + ret = strcmp(left_name, right_name);
> + if (ret)
> + return ret;
> + }
> +
> + return _sort__sym_cmp(left->sym, right->sym);
> }
>
> static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
> @@ -753,7 +776,7 @@ static void c2c_he__free_hierarchy(struct hist_entry *he);
> /*
> * Free a function-view histogram entry (hist_entry_ops::free).
> */
> -static void __maybe_unused c2c_function_he_free(void *ptr)
> +static void c2c_function_he_free(void *ptr)
> {
> struct hist_entry *he = ptr;
> struct c2c_hist_entry *c2c_he;
> @@ -768,11 +791,6 @@ static void __maybe_unused c2c_function_he_free(void *ptr)
>
> c2c_he__free_hierarchy(he);
>
> - zfree(&c2c_he->nodeset);
> - zfree(&c2c_he->cpuset);
> - zfree(&c2c_he->nodestr);
> - zfree(&c2c_he->node_stats);
> -
> free(c2c_he);
> }
>
> @@ -843,3 +861,282 @@ static int __maybe_unused c2c_he__prune_empty_writers(struct hist_entry *l1_he)
> }
> return surviving;
> }
> +
> +static void *c2c_function_he_zalloc(size_t size)
> +{
> + struct c2c_hist_entry *c2c_he = zalloc(sizeof(*c2c_he) + size);
> +
> + if (!c2c_he)
> + return NULL;
> +
> + init_stats(&c2c_he->cstats.lcl_hitm);
> + init_stats(&c2c_he->cstats.rmt_hitm);
> + init_stats(&c2c_he->cstats.lcl_peer);
> + init_stats(&c2c_he->cstats.rmt_peer);
> + init_stats(&c2c_he->cstats.load);
> +
> + return &c2c_he->he;
> +}
> +
> +/* Entry operations for function view */
> +static struct hist_entry_ops c2c_function_entry_ops = {
> + .new = c2c_function_he_zalloc,
> + .free = c2c_function_he_free,
> +};
> +
> +static struct c2c_hist_entry *
> +c2c_child_entry__alloc(struct hist_entry *parent_he, struct hist_entry *src_he,
> + int depth, u64 ip)
> +{
> + struct c2c_hist_entry *child_c2c;
> + struct hist_entry *child_he;
> +
> + /* Function-view children never own or display callchains. */
> + child_he = c2c_function_he_zalloc(0);
> + if (!child_he)
> + return NULL;
> +
> + child_c2c = container_of(child_he, struct c2c_hist_entry, he);
> + child_he->ops = &c2c_function_entry_ops;
> + map_symbol__copy(&child_he->ms, &src_he->ms);
> +
> + if (src_he->mem_info) {
> + child_he->mem_info = mem_info__clone(src_he->mem_info);
> + if (!child_he->mem_info)
> + goto out_free;
> + }
> +
> + child_he->thread = thread__get(src_he->thread);
> + child_he->cpumode = src_he->cpumode;
> + child_he->cpu = src_he->cpu;
> + child_he->socket = src_he->socket;
> + child_he->level = src_he->level;
> + child_he->ip = ip;
> +
> + child_he->parent_he = parent_he;
> + child_he->depth = depth;
> + child_he->leaf = (depth >= 2);
> + child_he->hists = &c2c_ext.function_hists.hists;
> + child_he->filtered = false;
> + child_he->unfolded = false;
> + child_he->has_children = false;
> + child_he->has_no_entry = false;
> + child_he->nr_rows = 0;
> + child_he->row_offset = 0;
> +
> + memset(&child_he->stat, 0, sizeof(child_he->stat));
> + child_he->hroot_in = RB_ROOT_CACHED;
> + child_he->hroot_out = RB_ROOT_CACHED;
> + INIT_LIST_HEAD(&child_he->pairs.node);
> + child_he->hpp_list = &c2c_ext.function_hists.list;
> + if (symbol_conf.cumulate_callchain) {
> + child_he->stat_acc = calloc(1, sizeof(struct he_stat));
> + if (!child_he->stat_acc)
> + goto out_free;
> + }
> +
> + return child_c2c;
> +
> +out_free:
> + hist_entry__delete(child_he);
> + return NULL;
> +}
> +
> +static void
> +c2c_child_entry__insert(struct hist_entry *parent_he, struct hist_entry *child_he,
> + struct rb_node **p, struct rb_node *rb_parent, bool leftmost)
> +{
> + rb_link_node(&child_he->rb_node, rb_parent, p);
> + rb_insert_color_cached(&child_he->rb_node, &parent_he->hroot_out, leftmost);
> +
> + parent_he->has_children = true;
> + parent_he->leaf = false;
> +}
> +
> +static __maybe_unused struct hist_entry *
> +c2c_function_hists__level1_entry(struct symbol *sym,
> + struct hist_entry *detail_he,
> + struct thread *synthetic_thread)
> +{
> + struct addr_location al;
> + struct perf_sample sample = {};
> + struct mem_info *mi;
> + struct hist_entry *he;
> + /*
> + * Key the level-1 entry by the function, not by a specific code
> + * address: use the symbol start so every instruction address inside
> + * the same function collapses into one entry. This makes level 1 a
> + * true "function view" rather than a per-code-address view.
> + */
> + u64 sym_start = (sym && detail_he->ms.map) ?
> + map__unmap_ip(detail_he->ms.map, sym->start) : detail_he->ip;
> +
> + mi = mem_info__new();
> + if (!mi)
> + return NULL;
> +
> + mem_info__iaddr(mi)->addr = sym_start;
> + /* mem_info__put() will map_symbol__exit() these, so take refs. */
> + mem_info__iaddr(mi)->ms.thread = thread__get(detail_he->ms.thread);
> + mem_info__iaddr(mi)->ms.map = map__get(detail_he->ms.map);
> + mem_info__iaddr(mi)->ms.sym = sym;
> + mem_info__daddr(mi)->addr = 0;
> +
> + addr_location__init(&al);
> + al.thread = thread__get(synthetic_thread);
> + al.map = map__get(detail_he->ms.map);
> + al.sym = sym;
> + al.addr = sym_start;
> + al.level = detail_he->level;
> + al.cpumode = detail_he->cpumode;
> + al.cpu = 0;
> + al.socket = 0;
> + al.filtered = 0;
> + al.latency = 0;
> +
> + /*
> + * Synthetic sample: period/weight are placeholders only. The real
> + * c2c counters live in c2c_hist_entry::stats and are added via
> + * hist_entry__add_c2c_stats(); no function-view column or sort key
> + * reads he->stat.period/nr_events, so the +1 that __hists__add_entry()
> + * accrues on each dedup hit has no effect on what is displayed.
> + */
> + sample.period = 1;
> + sample.weight = 1;
> + sample.ip = sym_start;
> + sample.pid = thread__pid(synthetic_thread);
> + sample.tid = thread__tid(synthetic_thread);
> + sample.cpu = 0;
> +
> + /* Add entry - histogram handles dedup */
> + he = hists__add_entry_ops(&c2c_ext.function_hists.hists,
> + &c2c_function_entry_ops,
> + &al, NULL, NULL, mi,
> + NULL, &sample, true);
> +
> + addr_location__exit(&al);
> + mem_info__put(mi);
> +
> + if (he)
> + he->hpp_list = &c2c_ext.function_hists.list;
> +
> + return he;
> +}
> +
> +/*
> + * Level 2: a function that writes a cacheline the level-1 function reads,
> + * keyed by the DSO display name and symbol, consistently with perf's symbol
> + * sort semantics. All code addresses and cachelines for the same writer
> + * function aggregate into one row.
> + */
> +static __maybe_unused struct c2c_hist_entry *
> +c2c_function_hists__level2_entry(struct c2c_hist_entry *level1_c2c,
> + struct symbol *sym, struct hist_entry *detail_he)
> +{
> + struct hist_entry *level1_he = &level1_c2c->he;
> + struct rb_node **p = &level1_he->hroot_out.rb_root.rb_node;
> + struct rb_node *parent = NULL;
> + struct c2c_hist_entry *level2_c2c;
> + bool leftmost = true;
> +
> + while (*p) {
> + struct hist_entry *iter = rb_entry(*p, struct hist_entry, rb_node);
> + struct map_symbol key = detail_he->ms;
> + int64_t cmp;
> +
> + key.sym = sym;
> + parent = *p;
> + cmp = c2c_function_cmp(&key, &iter->ms);
> +
> + if (cmp < 0) {
> + p = &parent->rb_left;
> + } else if (cmp > 0) {
> + p = &parent->rb_right;
> + leftmost = false;
> + } else {
> + return container_of(iter, struct c2c_hist_entry, he);
> + }
> + }
> +
> + /* Key by the function symbol start so all code addresses collapse. */
> + level2_c2c = c2c_child_entry__alloc(level1_he, detail_he, 1,
> + (sym && detail_he->ms.map) ?
> + map__unmap_ip(detail_he->ms.map, sym->start) :
> + hist_entry__iaddr(detail_he));
> + if (!level2_c2c)
> + return NULL;
> +
> + /* Key this level by the looked-up symbol, not detail_he's. */
> + level2_c2c->he.ms.sym = sym;
> + if (level2_c2c->he.mem_info)
> + mem_info__iaddr(level2_c2c->he.mem_info)->ms.sym = sym;
> +
> + c2c_child_entry__insert(level1_he, &level2_c2c->he, p, parent, leftmost);
> +
> + return level2_c2c;
> +}
> +
> +/* Level 3: one source cacheline where the L1/L2 functions contend. */
> +static __maybe_unused struct c2c_hist_entry *
> +c2c_function_hists__level3_entry(struct c2c_hist_entry *level2_c2c,
> + struct c2c_hist_entry *cacheline_src_he)
> +{
> + struct hist_entry *level2_he = &level2_c2c->he;
> + struct rb_node **p = &level2_he->hroot_out.rb_root.rb_node;
> + struct rb_node *parent = NULL;
> + struct c2c_hist_entry *level3_c2c;
> + bool leftmost = true;
> +
> + while (*p) {
> + struct c2c_hist_entry *iter_c2c =
> + rb_entry(*p, struct c2c_hist_entry, he.rb_node);
> +
> + parent = *p;
> + if (cacheline_src_he->cacheline_idx < iter_c2c->cacheline_idx) {
> + p = &parent->rb_left;
> + } else if (cacheline_src_he->cacheline_idx > iter_c2c->cacheline_idx) {
> + p = &parent->rb_right;
> + leftmost = false;
> + } else {
> + return iter_c2c;
> + }
> + }
> +
> + level3_c2c = c2c_child_entry__alloc(level2_he, &cacheline_src_he->he, 2,
> + hist_entry__iaddr(&cacheline_src_he->he));
> + if (!level3_c2c)
> + return NULL;
> + level3_c2c->cacheline_idx = cacheline_src_he->cacheline_idx;
> +
> + c2c_child_entry__insert(level2_he, &level3_c2c->he, p, parent, leftmost);
> +
> + return level3_c2c;
> +}
> +
> +struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he_selection)
> +{
> + struct c2c_hist_entry *c2c_he;
> + struct rb_node *nd;
> +
> + if (!c2c_ext.cl_hists || !he_selection || !he_selection->parent_he ||
> + !he_selection->parent_he->parent_he)
> + return NULL;
> +
> + c2c_he = container_of(he_selection, struct c2c_hist_entry, he);
> +
> + for (nd = rb_first_cached(&c2c_ext.cl_hists->hists.entries); nd;
> + nd = rb_next(nd)) {
> + struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
> + struct c2c_hist_entry *cacheline_he;
> +
> + if (he->filtered)
> + continue;
> +
> + cacheline_he = container_of(he, struct c2c_hist_entry, he);
> + if (cacheline_he->hists &&
> + cacheline_he->cacheline_idx == c2c_he->cacheline_idx)
> + return he;
> + }
> +
> + return NULL;
> +}
> diff --git a/tools/perf/util/c2c.h b/tools/perf/util/c2c.h
> index bd0c9d1c9a1a..f95e636edc0f 100644
> --- a/tools/perf/util/c2c.h
> +++ b/tools/perf/util/c2c.h
> @@ -98,4 +98,6 @@ struct c2c_fmt {
> void c2c_fmt_free(struct perf_hpp_fmt *fmt);
> bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b);
>
> +struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he);
> +
> #endif /* __PERF_UTIL_C2C_H */
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 7/9] perf c2c: build and finalize the function view hierarchy
2026-08-17 9:46 ` [PATCH v9 7/9] perf c2c: build and finalize the function view hierarchy Jiebin Sun
2026-08-17 9:54 ` sashiko-bot
@ 2026-08-17 21:11 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: Ian Rogers @ 2026-08-17 21:11 UTC (permalink / raw)
To: Jiebin Sun
Cc: namhyung, acme, mingo, peterz, adrian.hunter, alexander.shishkin,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, linux-perf-users, linux-kernel
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
>
> Add the builder that walks the top-level cacheline entries and, for each
> read-side function, correlates the functions that write the same lines
> (level 2) and the specific cachelines they contend over (level 3) within
> each retained detail histogram. Aggregate the write traffic per contending
> function, resort by store count, and prune writers/functions with no
> contention. The finalize pass then computes the Cycles % denominator from
> the surviving level-1 entries after pruning, so the column shows each
> function's share of the functions retained in the table rather than of the
> whole recording -- the semantics documented for Cycles % in perf-c2c.txt.
>
> Expose c2c_function__build() and c2c_function__reset() for the TUI front
> end added by the next patch. The builder requires iaddr in the cacheline
> coalescing fields and returns the completed hists through an output
> argument. Validate the inputs before replacing an existing model.
>
> Function-view entries do not carry callchains. Suppress callchain handling
> while building and tearing down the model so the common API does not depend
> on the caller's current callchain setting.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> Cc: Adrian Hunter <adrian.hunter@intel.com>
> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> Cc: Ian Rogers <irogers@google.com>
> Cc: Ingo Molnar <mingo@redhat.com>
> Cc: James Clark <james.clark@linaro.org>
> Cc: Jiri Olsa <jolsa@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Namhyung Kim <namhyung@kernel.org>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Thomas Falcon <thomas.falcon@intel.com>
> Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/util/c2c-function.c | 530 ++++++++++++++++++++++++++++++++-
> tools/perf/util/c2c.h | 10 +
> 2 files changed, 525 insertions(+), 15 deletions(-)
>
> diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
> index c005fb5454a7..5b6a06a5a067 100644
> --- a/tools/perf/util/c2c-function.c
> +++ b/tools/perf/util/c2c-function.c
> @@ -45,9 +45,9 @@ struct c2c_function_model {
> bool symbol_full;
> };
>
> -static struct c2c_function_model c2c_ext __maybe_unused;
> +static struct c2c_function_model c2c_ext;
>
> -static inline __maybe_unused u64 c2c_hitm_count(const struct c2c_stats *stats)
> +static inline u64 c2c_hitm_count(const struct c2c_stats *stats)
> {
> return stats->tot_hitm;
> }
> @@ -82,7 +82,7 @@ static int64_t c2c_function_cmp(const struct map_symbol *left,
> return _sort__sym_cmp(left->sym, right->sym);
> }
>
> -static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
> +static inline u64 hist_entry__iaddr(struct hist_entry *he)
> {
> if (he->mem_info)
> return mem_info__iaddr(he->mem_info)->addr;
> @@ -219,7 +219,7 @@ static u64 c2c_hist_entry__cycles(struct c2c_hist_entry *c2c_he)
> }
>
> /* Sum c2c_hist_entry__cycles() across all level-1 entries. */
> -static u64 __maybe_unused c2c_ext__total_cycles(void)
> +static u64 c2c_ext__total_cycles(void)
> {
> struct rb_node *nd;
> u64 total = 0;
> @@ -359,7 +359,7 @@ cycles_percent_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
>
> c2c_he = container_of(he, struct c2c_hist_entry, he);
> fn_cycles = c2c_hist_entry__cycles(c2c_he);
> - /* Populated by build_function_view_hierarchy() once the L1 tree is built. */
> + /* Populated by c2c_function__build() once the L1 tree is built. */
> total_cycles = c2c_ext.total_cycles;
> pct = total_cycles > 0 ? (double)fn_cycles / total_cycles * 100.0 : 0.0;
>
> @@ -665,7 +665,7 @@ function_hpp_list__parse(struct perf_hpp_list *hpp_list,
> return ret;
> }
>
> -static int __maybe_unused
> +static int
> c2c_function_hists__init(struct c2c_hists *hists,
> const char *sort,
> int nr_header_lines,
> @@ -680,7 +680,7 @@ c2c_function_hists__init(struct c2c_hists *hists,
> return function_hpp_list__parse(&hists->list, /*output=*/NULL, sort, env);
> }
>
> -static int __maybe_unused
> +static int
> c2c_function_hists__reinit(struct c2c_hists *c2c_hists,
> const char *output,
> const char *sort,
> @@ -730,8 +730,8 @@ static void c2c_stats_merge(struct stats *dest, const struct stats *src)
> }
>
> /* Merge compute_stats during function aggregation. */
> -static void __maybe_unused c2c_add_cstats(struct compute_stats *dest,
> - const struct compute_stats *src)
> +static void c2c_add_cstats(struct compute_stats *dest,
> + const struct compute_stats *src)
> {
> c2c_stats_merge(&dest->rmt_hitm, &src->rmt_hitm);
> c2c_stats_merge(&dest->lcl_hitm, &src->lcl_hitm);
> @@ -740,8 +740,8 @@ static void __maybe_unused c2c_add_cstats(struct compute_stats *dest,
> c2c_stats_merge(&dest->load, &src->load);
> }
>
> -static bool __maybe_unused hist_entry__add_c2c_stats(struct hist_entry *he,
> - const struct c2c_stats *stats)
> +static bool hist_entry__add_c2c_stats(struct hist_entry *he,
> + const struct c2c_stats *stats)
> {
> u64 nr_events = c2c_hitm_count(stats) + stats->rmt_peer + stats->lcl_peer;
> u64 weight1 = c2c_hitm_count(stats);
> @@ -832,7 +832,7 @@ static void c2c_he__free_hierarchy(struct hist_entry *he)
> * line, so this is mainly a safety net. Returns the number of surviving
> * writers.
> */
> -static int __maybe_unused c2c_he__prune_empty_writers(struct hist_entry *l1_he)
> +static int c2c_he__prune_empty_writers(struct hist_entry *l1_he)
> {
> struct rb_node *nd;
> int surviving = 0;
> @@ -953,7 +953,7 @@ c2c_child_entry__insert(struct hist_entry *parent_he, struct hist_entry *child_h
> parent_he->leaf = false;
> }
>
> -static __maybe_unused struct hist_entry *
> +static struct hist_entry *
> c2c_function_hists__level1_entry(struct symbol *sym,
> struct hist_entry *detail_he,
> struct thread *synthetic_thread)
> @@ -1029,7 +1029,7 @@ c2c_function_hists__level1_entry(struct symbol *sym,
> * sort semantics. All code addresses and cachelines for the same writer
> * function aggregate into one row.
> */
> -static __maybe_unused struct c2c_hist_entry *
> +static struct c2c_hist_entry *
> c2c_function_hists__level2_entry(struct c2c_hist_entry *level1_c2c,
> struct symbol *sym, struct hist_entry *detail_he)
> {
> @@ -1077,7 +1077,7 @@ c2c_function_hists__level2_entry(struct c2c_hist_entry *level1_c2c,
> }
>
> /* Level 3: one source cacheline where the L1/L2 functions contend. */
> -static __maybe_unused struct c2c_hist_entry *
> +static struct c2c_hist_entry *
> c2c_function_hists__level3_entry(struct c2c_hist_entry *level2_c2c,
> struct c2c_hist_entry *cacheline_src_he)
> {
> @@ -1140,3 +1140,503 @@ struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he_selection)
>
> return NULL;
> }
> +
> +/*
> + * Re-sort child entries of @parent_he by total store count, descending.
> + */
> +static void c2c_he__resort_by_stores(struct hist_entry *parent_he)
> +{
> + struct rb_root_cached new_root = RB_ROOT_CACHED;
> + struct rb_node *nd;
> +
> + if (!parent_he->has_children)
> + return;
> +
> + /* Extract all nodes and re-insert sorted by displayed store count */
> + while ((nd = rb_first_cached(&parent_he->hroot_out))) {
> + struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
> + u64 he_store = hist_entry__displayed_stores(he);
> + struct rb_node **p = &new_root.rb_root.rb_node;
> + struct rb_node *parent = NULL;
> + bool leftmost = true;
> + int cmp;
> +
> + /* Remove from current tree */
> + rb_erase_cached(&he->rb_node, &parent_he->hroot_out);
> +
> + /*
> + * Insert sorted by store count, descending. Use the displayed
> + * store count so a level-1 function and level-2 writer (whose own
> + * stats.store is 0 / partial) sort by the aggregated write traffic
> + * beneath them, not by their own store field.
> + */
> + while (*p) {
> + struct hist_entry *iter = rb_entry(*p, struct hist_entry, rb_node);
> + u64 iter_store = hist_entry__displayed_stores(iter);
> +
> + parent = *p;
> + if (he_store != iter_store) {
> + cmp = he_store > iter_store ? -1 : 1;
> + } else {
> + /* Stable tie-break: instruction address, name, then cacheline. */
> + u64 a = hist_entry__iaddr(he), b = hist_entry__iaddr(iter);
> +
> + if (a != b)
> + cmp = a < b ? -1 : 1;
> + else if (he->ms.sym && iter->ms.sym)
> + cmp = strcmp(he->ms.sym->name,
> + iter->ms.sym->name);
> + else
> + cmp = (iter->ms.sym ? 1 : 0) - (he->ms.sym ? 1 : 0);
> +
> + if (!cmp) {
> + struct c2c_hist_entry *he_c2c;
> + struct c2c_hist_entry *iter_c2c;
> +
> + he_c2c = container_of(he, struct c2c_hist_entry, he);
> + iter_c2c = container_of(iter, struct c2c_hist_entry, he);
> + if (he_c2c->cacheline_idx != iter_c2c->cacheline_idx)
> + cmp = he_c2c->cacheline_idx <
> + iter_c2c->cacheline_idx ? -1 : 1;
> + }
> + }
> +
> + if (cmp < 0) {
> + p = &parent->rb_left;
> + } else {
> + p = &parent->rb_right;
> + leftmost = false;
> + }
> + }
> +
> + rb_link_node(&he->rb_node, parent, p);
> + rb_insert_color_cached(&he->rb_node, &new_root, leftmost);
> + }
> +
> + parent_he->hroot_out = new_root;
> +}
> +
> +/* Initial per-cacheline capacity for the seen[] set; grown on demand. */
> +#define DEFAULT_SYMBOLS_PER_CL 64
> +
> +struct function_seen {
> + struct map_symbol ms;
> +};
> +
> +static bool function_seen__find(const struct function_seen *seen, int nr,
> + const struct map_symbol *ms)
> +{
> + int i;
> +
> + for (i = 0; i < nr; i++) {
> + if (!c2c_function_cmp(&seen[i].ms, ms))
> + return true;
> + }
> + return false;
> +}
> +
> +/* Aggregate stats from the cacheline-side entry @c2c_b into level 2/3 @dst. */
> +static bool c2c_he__add_sharing(struct c2c_hist_entry *dst, struct c2c_hist_entry *src)
> +{
> + /* Do the fallible update first so a failure leaves dst unmodified. */
> + if (!hist_entry__add_c2c_stats(&dst->he, &src->stats))
> + return false;
> +
> + c2c_add_stats(&dst->stats, &src->stats);
> + c2c_add_cstats(&dst->cstats, &src->cstats);
> + return true;
> +}
> +
> +/*
> + * Process one cacheline: for every function reading it, create/update its
> + * level-1 function entry, then for each function that writes the line
> + * add it as a level-2 writer and add this cacheline as a level-3 child.
> + */
> +static int c2c_function__process_cl(struct c2c_hist_entry *cacheline_he,
> + struct thread *synthetic_thread)
> +{
> + struct rb_node *nd_a, *nd_b;
> + struct function_seen *seen = NULL;
> + int nr_seen = 0, nr_alloc = 0;
> + int ret = 0;
> +
> + for (nd_a = rb_first_cached(&cacheline_he->hists->hists.entries); nd_a;
> + nd_a = rb_next(nd_a)) {
> + struct hist_entry *he_a = rb_entry(nd_a, struct hist_entry, rb_node);
> + struct c2c_hist_entry *c2c_a;
> + struct hist_entry *level1_he;
> + struct c2c_hist_entry *level1_c2c;
> +
> + if (!he_a->ms.sym || he_a->filtered)
> + continue;
> +
> + c2c_a = container_of(he_a, struct c2c_hist_entry, he);
> + if (c2c_a->stats.load == 0)
> + continue;
> +
> + level1_he = c2c_function_hists__level1_entry(he_a->ms.sym,
> + he_a, synthetic_thread);
> + if (!level1_he) {
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + level1_c2c = container_of(level1_he, struct c2c_hist_entry, he);
> +
> + /*
> + * Aggregate every source entry into its level-1 function parent.
> + * level1_he is keyed by symbol, so all code addresses inside the
> + * same function collapse into one parent. When the cacheline view
> + * splits a function into siblings (different code addresses, or
> + * --coalesce pid/tid/dso), each sibling holds a DISJOINT slice of the
> + * traffic, so summing them here is correct accumulation, not
> + * double counting. The seen[] set below therefore guards only the
> + * subtree build (to avoid building a function's level-2/3 subtree
> + * twice for the same cacheline), never this L1 update. Update
> + * he->stat first; on failure leave the aggregates untouched.
> + */
> + if (!hist_entry__add_c2c_stats(level1_he, &c2c_a->stats)) {
> + ret = -ENOMEM;
> + goto out;
> + }
> + c2c_add_stats(&level1_c2c->stats, &c2c_a->stats);
> + c2c_add_cstats(&level1_c2c->cstats, &c2c_a->cstats);
> + c2c_add_stats(&c2c_ext.function_hists.stats, &c2c_a->stats);
> +
> + /* Build this function's subtree for this cacheline only once. */
> + if (function_seen__find(seen, nr_seen, &he_a->ms))
> + continue;
> +
> + if (nr_seen == nr_alloc) {
> + struct function_seen *tmp;
> + int new_alloc = nr_alloc ? nr_alloc * 2 : DEFAULT_SYMBOLS_PER_CL;
> +
> + tmp = reallocarray(seen, new_alloc, sizeof(*seen));
> + if (!tmp) {
> + ret = -ENOMEM;
> + goto out;
> + }
> + seen = tmp;
> + nr_alloc = new_alloc;
> + }
> + seen[nr_seen].ms = he_a->ms;
> + nr_seen++;
> +
> + for (nd_b = rb_first_cached(&cacheline_he->hists->hists.entries); nd_b;
> + nd_b = rb_next(nd_b)) {
> + struct hist_entry *he_b = rb_entry(nd_b, struct hist_entry, rb_node);
> + struct c2c_hist_entry *c2c_b, *level2_c2c, *level3_c2c;
> +
> + if (!he_b->ms.sym || he_b->filtered)
> + continue;
> +
> + c2c_b = container_of(he_b, struct c2c_hist_entry, he);
> +
> + /*
> + * The level-1 function contributes read-side load weight for this
> + * cacheline. Associate it with functions sampled storing to the
> + * same line.
> + * The writer can be the same function; after detail coalescing and
> + * function-level grouping there is not enough identity to attribute
> + * that case to a specific thread.
> + * Only writers are contending functions, so keep the ones
> + * that actually store into the line.
> + */
> + if (c2c_b->stats.store == 0)
> + continue;
> +
> + /* Level 2: the writing function (aggregated across cachelines). */
> + level2_c2c = c2c_function_hists__level2_entry(level1_c2c, he_b->ms.sym,
> + he_b);
> + if (!level2_c2c || !c2c_he__add_sharing(level2_c2c, c2c_b)) {
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + /* Level 3: the specific cacheline they contend over. */
> + level3_c2c = c2c_function_hists__level3_entry(level2_c2c,
> + cacheline_he);
> + if (!level3_c2c || !c2c_he__add_sharing(level3_c2c, c2c_b)) {
> + ret = -ENOMEM;
> + goto out;
> + }
> + }
> + }
> +
> +out:
> + free(seen);
> + return ret;
> +}
> +
> +/*
> + * Remove a level-1 function that has no contended cachelines left. It is a
> + * normal (owned) hist_entry in function_hists, so mirror hists__delete_entry()
> + * for the no-collapse case: unlink from both trees, fix the counters, then
> + * delete. Its hroot_out is already empty after pruning.
> + */
> +static void c2c_function__drop_level1(struct hist_entry *he)
> +{
> + struct hists *hists = &c2c_ext.function_hists.hists;
> +
> + rb_erase_cached(&he->rb_node_in, hists->entries_in);
> + rb_erase_cached(&he->rb_node, &hists->entries);
> +
> + --hists->nr_entries;
> + if (!he->filtered)
> + --hists->nr_non_filtered_entries;
> +
> + hist_entry__delete(he);
> +}
> +
> +/* Length of the identity text (symbol name or cacheline address) at @he. */
> +static int c2c_function__ident_len(struct hist_entry *he)
> +{
> + char buf[512];
> + char *symbuf;
> + size_t size;
> + int len;
> +
> + if (hist_entry__is_cacheline(he)) {
> + u64 addr = he->mem_info ?
> + cl_address(mem_info__daddr(he->mem_info)->addr, chk_double_cl) : 0;
> +
> + return scnprintf(buf, sizeof(buf), "0x%" PRIx64, addr);
> + }
> +
> + if (!he->ms.sym)
> + return 0;
> +
> + /*
> + * Match symbol_view_entry(): sort_sym adds the cpumode prefix and, in
> + * verbose mode, the address and DSO origin before the symbol name.
> + */
> + size = strlen(he->ms.sym->name) + 64;
> + symbuf = malloc(size);
> + if (!symbuf)
> + return size - 1;
> +
> + len = sort_sym.se_snprintf(he, symbuf, size, size - 1);
> + free(symbuf);
> + return len;
> +}
> +
> +/*
> + * Grow the symbol column so the deepest, longest identity cell fits. The
> + * generic hists__calc_col_len() only measures the top-level (L1) entries; the
> + * hand-linked L2 writers and L3 cacheline addresses live in hroot_out and are
> + * never measured, so with a short L1 name the indented L2/L3 text would be
> + * truncated. Account for the per-level indent and the folded-sign prefix.
> + */
> +static void c2c_function__update_symbol_width(struct hist_entry *he)
> +{
> + struct hists *hists = &c2c_ext.function_hists.hists;
> + int need = he->depth * C2C_FUNC_INDENT + C2C_FUNC_FOLD_WIDTH +
> + c2c_function__ident_len(he);
> +
> + if (need > hists__col_len(hists, HISTC_SYMBOL))
> + hists__set_col_len(hists, HISTC_SYMBOL, need);
> +}
> +
> +/*
> + * Prune writers with no stores, drop functions left with no contending
> + * writer, sort the survivors by store count, then compute the global total.
> + */
> +static void c2c_function__finalize(void)
> +{
> + struct rb_node *nd_l1;
> +
> + nd_l1 = rb_first_cached(&c2c_ext.function_hists.hists.entries);
> + while (nd_l1) {
> + struct hist_entry *he_l1 = rb_entry(nd_l1, struct hist_entry, rb_node);
> + struct rb_node *next_l1 = rb_next(nd_l1);
> + struct rb_node *nd_l2;
> +
> + /* Drop writers with no stores before sorting. */
> + if (!he_l1->has_children || !c2c_he__prune_empty_writers(he_l1)) {
> + /* No contending writer: this function is not shared. */
> + c2c_function__drop_level1(he_l1);
> + nd_l1 = next_l1;
> + continue;
> + }
> +
> + c2c_he__resort_by_stores(he_l1);
> + c2c_function__update_symbol_width(he_l1);
> +
> + for (nd_l2 = rb_first_cached(&he_l1->hroot_out); nd_l2;
> + nd_l2 = rb_next(nd_l2)) {
> + struct hist_entry *he_l2 = rb_entry(nd_l2, struct hist_entry, rb_node);
> + struct rb_node *nd_l3;
> +
> + c2c_function__update_symbol_width(he_l2);
> +
> + if (he_l2->has_children)
> + c2c_he__resort_by_stores(he_l2);
> +
> + for (nd_l3 = rb_first_cached(&he_l2->hroot_out); nd_l3;
> + nd_l3 = rb_next(nd_l3)) {
> + struct hist_entry *he_l3 = rb_entry(nd_l3, struct hist_entry,
> + rb_node);
> +
> + c2c_function__update_symbol_width(he_l3);
> + }
> + }
> +
> + nd_l1 = next_l1;
> + }
> +
> + /*
> + * Compute the Cycles % denominator from the surviving level-1 entries
> + * after pruning, so the column shows each function's share of the
> + * functions retained in this table -- not of the whole recording. See
> + * the Cycles % description in perf-c2c.txt.
> + */
> + c2c_ext.total_cycles = c2c_ext__total_cycles();
> +}
> +
> +/*
> + * Release all per-visit function-view state. Keep the hists object itself
> + * initialized so its mutex is initialized exactly once across TAB re-entry.
> + */
> +void c2c_function__reset(void)
> +{
> + bool saved_use_callchain = symbol_conf.use_callchain;
> +
> + /*
> + * Function-view entries never carry callchains. Keep their generic
> + * destructor independent of the caller's current callchain setting.
> + */
> + symbol_conf.use_callchain = false;
> + hists__delete_all_entries(&c2c_ext.function_hists.hists);
> + if (c2c_ext.function_hists.list.fields.next)
> + perf_hpp__reset_output_field(&c2c_ext.function_hists.list);
> +
> + memset(&c2c_ext.function_hists.stats, 0,
> + sizeof(c2c_ext.function_hists.stats));
> + c2c_ext.total_cycles = 0;
> + c2c_ext.cl_hists = NULL;
> + c2c_ext.cl_sort = NULL;
> + c2c_ext.symbol_full = false;
> + symbol_conf.use_callchain = saved_use_callchain;
> +}
> +
> +static bool c2c_function__has_iaddr(const char *cl_sort)
> +{
> + const char *field = cl_sort;
> +
> + while (field && *field) {
> + const char *end = strchr(field, ',');
> + size_t len = end ? (size_t)(end - field) : strlen(field);
> +
> + if (len == sizeof("iaddr") - 1 && !strncmp(field, "iaddr", len))
> + return true;
> + field = end ? end + 1 : NULL;
> + }
> + return false;
> +}
> +
> +/*
> + * Build the three-level function view in a single pass over the cacheline
> + * entries:
> + * L1: read-side functions (aggregated across all their code addresses)
> + * L2: writing functions contending with each level-1 function
> + * L3: shared cachelines for each function pair
> + */
> +int c2c_function__build(struct c2c_hists *cl_hists, const char *cl_sort,
> + bool symbol_full, struct hists **hists)
> +{
> + static const char output_fields[] =
> + "cycles_percent,total_stores,symbol_view";
> + static bool hists_initialized;
> + struct rb_node *nd_cl;
> + bool saved_use_callchain;
> + int ret;
> +
> + if (!hists)
> + return -EINVAL;
> + *hists = NULL;
> +
> + if (!cl_hists || !cl_sort)
> + return -EINVAL;
> + if (!c2c_function__has_iaddr(cl_sort))
> + return -EOPNOTSUPP;
> +
> + saved_use_callchain = symbol_conf.use_callchain;
> + symbol_conf.use_callchain = false;
> + c2c_function__reset();
> +
> + c2c_ext.cl_hists = cl_hists;
> + c2c_ext.cl_sort = cl_sort;
> + c2c_ext.symbol_full = symbol_full;
> +
> + /*
> + * __hists__init() (called by c2c_function_hists__init()) also
> + * mutex_init()s the hists lock, so only run it once for this static
> + * hists; on re-entry just re-parse the columns via reinit().
> + */
> + if (!hists_initialized) {
> + ret = c2c_function_hists__init(&c2c_ext.function_hists,
> + "symbol_view", 2, NULL);
> + hists_initialized = true;
> + } else {
> + ret = c2c_function_hists__reinit(&c2c_ext.function_hists,
> + /*output=*/NULL, "symbol_view", NULL);
> + }
> + if (ret)
> + goto out_reset;
> +
> + nd_cl = rb_first_cached(&c2c_ext.cl_hists->hists.entries);
> +
> + /* An empty C2C report yields an empty (but valid) function view. */
> + for (; nd_cl; nd_cl = rb_next(nd_cl)) {
> + struct hist_entry *he_cl = rb_entry(nd_cl, struct hist_entry, rb_node);
> + struct c2c_hist_entry *cacheline_he = container_of(he_cl,
> + struct c2c_hist_entry, he);
> + struct thread *synthetic_thread = he_cl->thread;
> +
> + /*
> + * Detail hists are finalized only for cachelines retained by the
> + * top-level C2C filter. Among those, include any line with sharing
> + * activity, not just HITM.
> + */
> + if (he_cl->filtered ||
> + (c2c_hitm_count(&cacheline_he->stats) == 0 &&
> + cacheline_he->stats.tot_peer == 0 &&
> + cacheline_he->stats.store == 0 &&
> + cacheline_he->stats.load == 0) ||
> + !cacheline_he->hists ||
> + RB_EMPTY_ROOT(&cacheline_he->hists->hists.entries.rb_root) ||
> + !he_cl->mem_info || !synthetic_thread)
> + continue;
> +
> + ret = c2c_function__process_cl(cacheline_he, synthetic_thread);
> + if (ret)
> + goto out_err;
> + }
> +
> + ret = c2c_function_hists__reinit(&c2c_ext.function_hists, output_fields,
> + "cycles_percent", NULL);
> + if (ret)
> + goto out_err;
> +
> + hists__collapse_resort(&c2c_ext.function_hists.hists, NULL);
> + hists__output_resort(&c2c_ext.function_hists.hists, NULL);
> +
> + c2c_function__finalize();
> +
> + *hists = &c2c_ext.function_hists.hists;
> + symbol_conf.use_callchain = saved_use_callchain;
> + return 0;
> +
> +out_err:
> + /*
> + * On error, migrate any entries still in entries_in to entries and
> + * delete them, so a later rebuild does not strand them (the top-level
> + * __hists__init() memset would otherwise lose the pointers).
> + */
> + hists__collapse_resort(&c2c_ext.function_hists.hists, NULL);
> + hists__output_resort(&c2c_ext.function_hists.hists, NULL);
> +out_reset:
> + c2c_function__reset();
> + symbol_conf.use_callchain = saved_use_callchain;
> + return ret;
> +}
> diff --git a/tools/perf/util/c2c.h b/tools/perf/util/c2c.h
> index f95e636edc0f..6548b9ba81f1 100644
> --- a/tools/perf/util/c2c.h
> +++ b/tools/perf/util/c2c.h
> @@ -98,6 +98,16 @@ struct c2c_fmt {
> void c2c_fmt_free(struct perf_hpp_fmt *fmt);
> bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b);
>
> +/*
> + * Build the function-view hierarchy. Returns -EOPNOTSUPP when @cl_sort lacks
> + * iaddr. On success, *@hists remains valid until the next
> + * c2c_function__build() or c2c_function__reset(). On failure, *@hists is
> + * NULL.
> + */
> +int c2c_function__build(struct c2c_hists *cl_hists, const char *cl_sort,
> + bool symbol_full, struct hists **hists);
> +void c2c_function__reset(void);
> +/* Valid only between a successful build and c2c_function__reset(). */
> struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he);
>
> #endif /* __PERF_UTIL_C2C_H */
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 8/9] perf c2c: add function view browser UI and cacheline detail
2026-08-17 9:46 ` [PATCH v9 8/9] perf c2c: add function view browser UI and cacheline detail Jiebin Sun
2026-08-17 9:51 ` sashiko-bot
@ 2026-08-17 21:12 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: Ian Rogers @ 2026-08-17 21:12 UTC (permalink / raw)
To: Jiebin Sun
Cc: namhyung, acme, mingo, peterz, adrian.hunter, alexander.shishkin,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, linux-perf-users, linux-kernel
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
>
> Add the browser front end: create/run/delete the hist_browser and add the
> title. The d shortcut opens the existing per-cacheline detail view for the
> selected level-3 cacheline. Level-3 entries retain the source cacheline
> index, so the shortcut can locate the original entry without relying on a
> potentially ambiguous virtual address.
>
> Report a warning when the common model rejects a cacheline coalescing field
> list without `iaddr`. Without it, the detail histograms may already have
> merged samples from different functions and cannot support reliable
> function attribution.
>
> Keep visible-row accounting local to the function view by wrapping the
> generic browser refresh callback and recounting the currently reachable
> hierarchy before each redraw. This keeps navigation correct when a level-1
> row is collapsed while level-3 descendants remain expanded, without adding
> C2C-specific hooks to the shared hist_browser. Also handle Ctrl-C like the
> other function-view exit keys.
>
> Keep callchains hidden while the function browser runs, restoring the
> user's setting while opening the cacheline detail view.
>
> Wire the builder into perf_c2c__browse_function_view().
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> Cc: Adrian Hunter <adrian.hunter@intel.com>
> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> Cc: Ian Rogers <irogers@google.com>
> Cc: Ingo Molnar <mingo@redhat.com>
> Cc: James Clark <james.clark@linaro.org>
> Cc: Jiri Olsa <jolsa@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Namhyung Kim <namhyung@kernel.org>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Thomas Falcon <thomas.falcon@intel.com>
> Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/builtin-c2c.c | 10 ++
> tools/perf/ui/browsers/Build | 1 +
> tools/perf/ui/browsers/c2c-function.c | 209 ++++++++++++++++++++++++++
> tools/perf/util/c2c.h | 22 +++
> 4 files changed, 242 insertions(+)
> create mode 100644 tools/perf/ui/browsers/c2c-function.c
>
> diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
> index 16b00a36fdfc..715b75d42f2a 100644
> --- a/tools/perf/builtin-c2c.c
> +++ b/tools/perf/builtin-c2c.c
> @@ -2745,11 +2745,18 @@ perf_c2c_browser__new(struct hists *hists)
>
> static int perf_c2c__hists_browse(struct hists *hists)
> {
> + struct c2c_function_view_args func_args = {
> + .cl_hists = &c2c.hists,
> + .cl_sort = c2c.cl_sort,
> + .symbol_full = c2c.symbol_full,
> + .browse_cacheline = perf_c2c__browse_cacheline,
> + };
> struct hist_browser *browser;
> int key = -1;
> static const char help[] =
> " d Display cacheline details \n"
> " ENTER Toggle callchains (if present) \n"
> + " TAB Switch to function view\n"
> " q Quit \n";
>
> browser = perf_c2c_browser__new(hists);
> @@ -2771,6 +2778,9 @@ static int perf_c2c__hists_browse(struct hists *hists)
> case 'd':
> perf_c2c__browse_cacheline(browser->he_selection);
> break;
> + case '\t':
> + perf_c2c__browse_function_view(&func_args);
> + break;
> case '?':
> ui_browser__help_window(&browser->b, help);
> break;
> diff --git a/tools/perf/ui/browsers/Build b/tools/perf/ui/browsers/Build
> index a07489e44765..ae67a2161f7d 100644
> --- a/tools/perf/ui/browsers/Build
> +++ b/tools/perf/ui/browsers/Build
> @@ -5,3 +5,4 @@ perf-ui-y += map.o
> perf-ui-y += scripts.o
> perf-ui-y += header.o
> perf-ui-y += res_sample.o
> +perf-ui-y += c2c-function.o
> diff --git a/tools/perf/ui/browsers/c2c-function.c b/tools/perf/ui/browsers/c2c-function.c
> new file mode 100644
> index 000000000000..3d23e9d84981
> --- /dev/null
> +++ b/tools/perf/ui/browsers/c2c-function.c
> @@ -0,0 +1,209 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * C2C function browser - TUI front end for function-level sharing analysis
> + */
> +
> +#include <errno.h>
> +#include <inttypes.h>
> +#include <stdlib.h>
> +#include <sys/ttydefaults.h>
> +#include <linux/rbtree.h>
> +#include <linux/zalloc.h>
> +
> +#include "../browser.h"
> +#include "../keysyms.h"
> +#include "../libslang.h"
> +#include "../ui.h"
> +#include "../../util/c2c.h"
> +#include "../../util/debug.h"
> +#include "../../util/hist.h"
> +#include "../../util/symbol.h"
> +#include "hists.h"
> +
> +struct c2c_function_browser {
> + struct hist_browser hb;
> + unsigned int (*orig_refresh)(struct ui_browser *browser);
> + int (*browse_cacheline)(struct hist_entry *he);
> +};
> +
> +/*
> + * Count visible entries in @root, descending only through visible, unfolded
> + * parents. Match hists__filter_entries(), which drives generic browser
> + * navigation, so the count cannot include rows the browser skips.
> + */
> +static u64
> +c2c_function__nr_visible_rows(struct rb_root_cached *root, float min_pcnt)
> +{
> + struct rb_node *nd;
> + u64 rows = 0;
> +
> + for (nd = rb_first_cached(root); nd; nd = rb_next(nd)) {
> + struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
> +
> + /*
> + * The generic refresh folds filtered parents and therefore hides
> + * their subtree. A percentage-rejected parent is merely skipped;
> + * if it is unfolded, qualifying descendants are still rendered.
> + */
> + if (he->filtered)
> + continue;
> +
> + if (hist_entry__get_percent_limit(he) >= min_pcnt)
> + rows++;
> + if (he->has_children && he->unfolded)
> + rows += c2c_function__nr_visible_rows(&he->hroot_out,
> + min_pcnt);
> + }
> + return rows;
> +}
> +
> +static void
> +c2c_function_browser__update_nr_entries(struct c2c_function_browser *browser)
> +{
> + u64 nr_entries;
> +
> + nr_entries = c2c_function__nr_visible_rows(&browser->hb.hists->entries,
> + browser->hb.min_pcnt);
> + browser->hb.nr_non_filtered_entries = nr_entries;
> + browser->hb.b.nr_entries = nr_entries;
> +}
> +
> +static unsigned int c2c_function_browser__refresh(struct ui_browser *ui_browser)
> +{
> + struct hist_browser *hist_browser = container_of(ui_browser, struct hist_browser, b);
> + struct c2c_function_browser *browser;
> +
> + browser = container_of(hist_browser, struct c2c_function_browser, hb);
> + c2c_function_browser__update_nr_entries(browser);
> + return browser->orig_refresh(ui_browser);
> +}
> +
> +static int c2c_function_browser__title(struct hist_browser *browser,
> + char *bf, size_t size)
> +{
> + scnprintf(bf, size,
> + "Shared Data Functions Table (%" PRIu64 " entries, sorted on Cycles %%)",
> + browser->hists->nr_non_filtered_entries);
> + return 0;
> +}
> +
> +static struct c2c_function_browser *
> +c2c_function_browser__new(struct hists *hists,
> + int (*browse_cacheline)(struct hist_entry *he))
> +{
> + struct c2c_function_browser *browser;
> +
> + if (!hists)
> + return NULL;
> +
> + browser = zalloc(sizeof(*browser));
> + if (!browser)
> + return NULL;
> +
> + hist_browser__init(&browser->hb, hists);
> + browser->orig_refresh = browser->hb.b.refresh;
> + browser->hb.b.refresh = c2c_function_browser__refresh;
> + browser->browse_cacheline = browse_cacheline;
> +
> + browser->hb.title = c2c_function_browser__title;
> + browser->hb.c2c_filter = true;
> + browser->hb.show_headers = true;
> + /* Keep title line count consistent with forcing headers on. */
> + browser->hb.b.extra_title_lines = hists->hpp_list->nr_header_lines;
> + browser->hb.min_pcnt = 0.0;
> +
> + return browser;
> +}
> +
> +static void c2c_function_browser__delete(struct c2c_function_browser *browser)
> +{
> + free(browser);
> +}
> +
> +static int
> +c2c_browser__browse_cacheline(struct c2c_function_browser *browser,
> + struct hist_entry *he_selection)
> +{
> + struct hist_entry *he = c2c_function__find_cacheline(he_selection);
> +
> + return he ? browser->browse_cacheline(he) : -1;
> +}
> +
> +int perf_c2c__browse_function_view(struct c2c_function_view_args *args)
> +{
> + struct c2c_function_browser *browser;
> + struct hists *hists;
> + bool saved_use_callchain = symbol_conf.use_callchain;
> + int key, ret;
> + static const char help[] =
> + " d Display details for the selected level-3 cacheline\n"
> + " e/+ Expand/collapse the selected entry\n"
> + " TAB/ESC/q/^C Return to the cacheline view\n";
> +
> + if (!args || !args->cl_hists || !args->browse_cacheline)
> + return -EINVAL;
> +
> + /*
> + * Function view does not display callchains; cacheline detail temporarily
> + * restores them.
> + */
> + symbol_conf.use_callchain = false;
> +
> + ret = c2c_function__build(args->cl_hists, args->cl_sort,
> + args->symbol_full, &hists);
> + if (ret) {
> + if (ret == -EOPNOTSUPP)
> + ui__warning("The function view requires iaddr in --coalesce.\n");
> + else
> + ui__error("Failed to build function view hierarchy (ret=%d)\n", ret);
> + goto out;
> + }
> +
> + browser = c2c_function_browser__new(hists, args->browse_cacheline);
> + if (!browser) {
> + ret = -ENOMEM;
> + goto out_reset;
> + }
> +
> + /* Reset abort key so we can receive Ctrl-C as a key. */
> + SLang_reset_tty();
> + SLang_init_tty(0, 0, 0);
> + SLtty_set_suspend_state(true);
> +
> + while (1) {
> + c2c_function_browser__update_nr_entries(browser);
> + key = hist_browser__run(&browser->hb, "? - help", true, 0);
> +
> + switch (key) {
> + case 'q':
> + case K_TAB:
> + case K_ESC:
> + case CTRL('c'):
> + goto browser_done;
> + case 'd':
> + /* Cacheline detail honors the user's callchain setting. */
> + symbol_conf.use_callchain = saved_use_callchain;
> + c2c_browser__browse_cacheline(browser, browser->hb.he_selection);
> + /*
> + * Preserve any toggle made in the detail view, then
> + * re-disable callchain for the function view.
> + */
> + saved_use_callchain = symbol_conf.use_callchain;
> + symbol_conf.use_callchain = false;
> + break;
> + case '?':
> + ui_browser__help_window(&browser->hb.b, help);
> + break;
> + default:
> + break;
> + }
> + }
> +
> +browser_done:
> + c2c_function_browser__delete(browser);
> +out_reset:
> + c2c_function__reset();
> +out:
> + symbol_conf.use_callchain = saved_use_callchain;
> + return ret;
> +}
> diff --git a/tools/perf/util/c2c.h b/tools/perf/util/c2c.h
> index 6548b9ba81f1..53f024e25d99 100644
> --- a/tools/perf/util/c2c.h
> +++ b/tools/perf/util/c2c.h
> @@ -110,4 +110,26 @@ void c2c_function__reset(void);
> /* Valid only between a successful build and c2c_function__reset(). */
> struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he);
>
> +/* Inputs and TUI callback supplied by the c2c command. */
> +struct c2c_function_view_args {
> + /* Source cacheline histograms used by the common model. */
> + struct c2c_hists *cl_hists;
> + /* --coalesce field list, used to require iaddr. */
> + const char *cl_sort;
> + /* Do not cap long symbol names. */
> + bool symbol_full;
> + /* Open the cacheline detail view for @he. */
> + int (*browse_cacheline)(struct hist_entry *he);
> +};
> +
> +#ifdef HAVE_SLANG_SUPPORT
> +int perf_c2c__browse_function_view(struct c2c_function_view_args *args);
> +#else
> +static inline int
> +perf_c2c__browse_function_view(struct c2c_function_view_args *args __maybe_unused)
> +{
> + return 0;
> +}
> +#endif
> +
> #endif /* __PERF_UTIL_C2C_H */
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 9/9] perf c2c: document function view in perf-c2c man page
2026-08-17 9:46 ` [PATCH v9 9/9] perf c2c: document function view in perf-c2c man page Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
@ 2026-08-17 21:16 ` Ian Rogers
1 sibling, 0 replies; 30+ messages in thread
From: Ian Rogers @ 2026-08-17 21:16 UTC (permalink / raw)
To: Jiebin Sun
Cc: namhyung, acme, mingo, peterz, adrian.hunter, alexander.shishkin,
james.clark, jolsa, mark.rutland, dapeng1.mi, thomas.falcon,
tianyou.li, wangyang.guo, linux-perf-users, linux-kernel
On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
>
> Describe the function view hierarchy (read-side function -> contending
> writer function -> shared cachelines), the per-level indentation, and the
> keys, with a worked example.
>
> Document that reliable function attribution requires `iaddr` in
> `--coalesce`, that the reader and writer may be the same function, and why
> the coalesced function view cannot distinguish same-thread from
> different-thread accesses in that case. Also document that verbose mode
> includes code addresses in function rows.
>
> Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> Cc: Adrian Hunter <adrian.hunter@intel.com>
> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> Cc: Ian Rogers <irogers@google.com>
> Cc: Ingo Molnar <mingo@redhat.com>
> Cc: James Clark <james.clark@linaro.org>
> Cc: Jiri Olsa <jolsa@kernel.org>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Namhyung Kim <namhyung@kernel.org>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Thomas Falcon <thomas.falcon@intel.com>
> Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> ---
> tools/perf/Documentation/perf-c2c.txt | 71 +++++++++++++++++++++++++++
> 1 file changed, 71 insertions(+)
>
> diff --git a/tools/perf/Documentation/perf-c2c.txt b/tools/perf/Documentation/perf-c2c.txt
> index e57a122b8719..8775889bc0a3 100644
> --- a/tools/perf/Documentation/perf-c2c.txt
> +++ b/tools/perf/Documentation/perf-c2c.txt
> @@ -365,6 +365,77 @@ TUI OUTPUT
> The TUI output provides interactive interface to navigate
> through cachelines list and to display offset details.
>
> +Pressing the 'TAB' key in the cacheline view switches to the function
> +view. The function view shows a three-level hierarchy of the symbolized
> +entries retained in the cacheline view, organized around functions rather
> +than cachelines. Levels 1 and 2 normally show function names, while level 3
> +shows cacheline addresses. Lower levels are indented beneath their parents.
> +Verbose mode also includes code addresses in function rows, and code addresses
> +remain available in the per-cacheline detail view ('d').
> +
> +The function view requires `iaddr` in the cacheline coalescing fields. If
> +`--coalesce` omits it, TAB reports that the view is unavailable rather than
> +attributing already-coalesced samples to an arbitrary function.
> +
> + Level 1: the read-side function, sorted by Cycles % (estimated load
> + cycles: HITM, peer-snoop and other-load cycles)
> + Level 2: the functions sampled writing the shared lines read by the
> + level-1 function, sorted by store count. This can be the same
> + function when it has both read and write samples
> + Level 3: the specific cachelines shared by the reader/writer pair
> +
> +The Cycles % value is the function's share of event-provided load
> +latency/weight estimates from cacheline-detail entries retained in the
> +current view. It can include non-HITM and non-peer loads coalesced into
> +entries that pass the C2C filter, so it is not a pure contention-cycle
> +percentage. The share is relative to the functions and entries retained
> +for the current report and is not comparable across recordings or different
> +`--coalesce` settings.
> +
> +The store count on a level-1 row is the number of sampled stores by writers
> +shown in the function view into the cachelines that function reads, including
> +stores from the same function. It decomposes into the level-2 writer rows;
> +each level-2 count in turn decomposes into that writer's stores on its level-3
> +cachelines. A level-3 count is therefore not the cacheline's total store
> +count. The level-1 value is not the number of stores made by the reader and
> +is not additive across level-1 rows: two functions reading the same line each
> +carry the stores into that line.
> +
> +Each function aggregates all of its code addresses into a single entry,
> +and a level-2 writer aggregates all of its shared cachelines, so a
> +reader/writer pair is a single row with its total shown -- there is no
> +need to sum a writer's traffic across cachelines by hand.
> +
> +In the function view the 'd' key opens the detail view of the selected
> +level-3 cacheline, 'e'/'+' expands or collapses the current entry, and 'TAB',
> +'ESC', 'q' or Ctrl-C returns to the cacheline view.
> +
> +For example, with the first two read-side functions collapsed and
> +dequeue_pushable_task expanded to show the functions writing the lines it
> +reads -- two of which are further expanded to their individual cachelines:
> +
> + Shared Data Functions Table (19 entries, sorted on Cycles %)
> + Cycles Store
> + % count Function / Contending function / Cacheline
> + ----------------------------------------------------------------------
> + + 35.67% 876 + [k] cpupri_set
> + + 24.31% 424 + [k] pull_rt_task
> + - 16.53% 555 - [k] dequeue_pushable_task
> + 145 - [k] pull_rt_task
> + 145 0xff2d0082809da080
> + 139 - [k] enqueue_pushable_task
> + 70 0xff2d00a2071f9640
> + 69 0xff2d0082809da000
> +
> +Here dequeue_pushable_task pays 16.53% of the estimated read-side load-cycle
> +cost. Its store count decomposes into its level-2 writers, and each writer's
> +count decomposes into its level-3 cachelines: pull_rt_task's 145 stores fall
> +on a single line, while enqueue_pushable_task's 139 stores split across two
> +lines (70 and 69). A writer can be the same function as the reader when it
> +has both read and write samples; after cacheline coalescing and
> +function-level grouping, the view cannot distinguish same-thread accesses
> +from different threads running the same function.
> +
> For details please refer to the help window by pressing '?' key.
>
> CREDITS
> --
> 2.52.0
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 2/9] perf c2c: add function view model skeleton
2026-08-17 20:53 ` Ian Rogers
@ 2026-08-17 22:48 ` Namhyung Kim
2026-08-17 22:53 ` Ian Rogers
0 siblings, 1 reply; 30+ messages in thread
From: Namhyung Kim @ 2026-08-17 22:48 UTC (permalink / raw)
To: Ian Rogers
Cc: Jiebin Sun, acme, mingo, peterz, adrian.hunter,
alexander.shishkin, james.clark, jolsa, mark.rutland, dapeng1.mi,
thomas.falcon, tianyou.li, wangyang.guo, linux-perf-users,
linux-kernel
Hello,
On Mon, Aug 17, 2026 at 01:53:46PM -0700, Ian Rogers wrote:
> On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
> >
> > Add the initial common model for the c2c function view: model state and
> > small helpers shared by the hierarchy construction and formatting added
> > in later patches.
> >
> > Build the model from util/ so it remains independent of the TUI and
> > command-private symbols.
> >
> > Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> > Cc: Adrian Hunter <adrian.hunter@intel.com>
> > Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> > Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> > Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> > Cc: Ian Rogers <irogers@google.com>
> > Cc: Ingo Molnar <mingo@redhat.com>
> > Cc: James Clark <james.clark@linaro.org>
> > Cc: Jiri Olsa <jolsa@kernel.org>
> > Cc: Mark Rutland <mark.rutland@arm.com>
> > Cc: Namhyung Kim <namhyung@kernel.org>
> > Cc: Peter Zijlstra <peterz@infradead.org>
> > Cc: Thomas Falcon <thomas.falcon@intel.com>
> > Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> > Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
> > ---
> > tools/perf/util/Build | 1 +
> > tools/perf/util/c2c-function.c | 66 ++++++++++++++++++++++++++++++++++
> > 2 files changed, 67 insertions(+)
> > create mode 100644 tools/perf/util/c2c-function.c
> >
> > diff --git a/tools/perf/util/Build b/tools/perf/util/Build
> > index 1dfd92cbe3b7..b26a0b1ddfa3 100644
> > --- a/tools/perf/util/Build
> > +++ b/tools/perf/util/Build
> > @@ -12,6 +12,7 @@ perf-util-y += block-info.o
> > perf-util-y += block-range.o
> > perf-util-y += build-id.o
> > perf-util-y += c2c.o
> > +perf-util-y += c2c-function.o
> > perf-util-y += cacheline.o
> > perf-util-$(CONFIG_LIBCAPSTONE) += capstone.o
> > perf-util-y += config.o
> > diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
> > new file mode 100644
> > index 000000000000..ca82425a28dc
> > --- /dev/null
> > +++ b/tools/perf/util/c2c-function.c
> > @@ -0,0 +1,66 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * C2C function model - function-level cacheline sharing analysis
> > + *
> > + * Displays a 3-level hierarchy showing which functions share cachelines:
> > + * Level 1: Read-side functions sorted by Cycles % (estimated load cycles)
> > + * Level 2: Functions sampled writing the shared lines read by level 1
> > + * Level 3: The specific cachelines where the two functions contend
> > + *
> > + * Builds the hierarchy from the existing cacheline histograms
> > + * (c2c_hist_entry->hists), reusing the shared c2c data structures.
> > + */
> > +
> > +#include <errno.h>
> > +#include <inttypes.h>
> > +#include <stdlib.h>
> > +#include <string.h>
> > +#include <tools/libc_compat.h> /* reallocarray */
> > +#include <linux/list.h>
> > +#include <linux/rbtree.h>
> > +#include <linux/zalloc.h>
> > +
> > +#include "addr_location.h"
> > +#include "c2c.h"
> > +#include "cacheline.h"
> > +#include "hist.h"
> > +#include "map.h"
> > +#include "mem-events.h"
> > +#include "mem-info.h"
> > +#include "sort.h"
> > +#include "symbol.h"
> > +#include "thread.h"
>
> nit: the number of #includes is somewhat generous here. I presume
> later patches will require these includes. To avoid everything
> depending on everything else it would be nice to use forward
> declarations when possible. For example, if the only reason for
> including the header file was to use a struct's name where it is
> passed as a pointer in a function declaration, ie in header files
> prefer:
>
> struct map;
> int foo(struct map *m);
>
> over
>
> #include "map.h"
> int foo(struct map *m);
I think they are actually used in the later patches in this series.
>
> > +
> > +struct c2c_function_model {
> > + struct c2c_hists function_hists;
> > + /* Total estimated cycles across all level-1 entries. */
> > + u64 total_cycles;
> > + /* Source cacheline histograms; not owned here. */
> > + struct c2c_hists *cl_hists;
> > + /* --coalesce field list, used to require iaddr. */
> > + const char *cl_sort;
> > + /* Do not cap long symbol names. */
> > + bool symbol_full;
> > +};
> > +
> > +static struct c2c_function_model c2c_ext __maybe_unused;
> > +
> > +static inline __maybe_unused u64 c2c_hitm_count(const struct c2c_stats *stats)
> > +{
> > + return stats->tot_hitm;
> > +}
> > +
> > +static inline __maybe_unused bool symbol_name_equal(struct symbol *a, struct symbol *b)
> > +{
> > + /* Two unknown symbols compare equal, matching cmp_null() in util/sort.c. */
> > + if (!a || !b)
> > + return a == b;
> > + return arch__compare_symbol_names(a->name, b->name) == 0;
>
> Sashiko rightly flagged this as not being cross-platform compatible,
> but this is a pre-existing issue that looks relatively easy to clean
> up but only really impacts PowerPC and so is hard for me to test. I'll
> try to do it anyway.
Thanks for your review. Yep, I think it can be handled separately.
Namhyung
>
> > +}
> > +
> > +static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
> > +{
> > + if (he->mem_info)
> > + return mem_info__iaddr(he->mem_info)->addr;
> > + return he->ip;
> > +}
> > --
> > 2.52.0
> >
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v9 2/9] perf c2c: add function view model skeleton
2026-08-17 22:48 ` Namhyung Kim
@ 2026-08-17 22:53 ` Ian Rogers
0 siblings, 0 replies; 30+ messages in thread
From: Ian Rogers @ 2026-08-17 22:53 UTC (permalink / raw)
To: Namhyung Kim
Cc: Jiebin Sun, acme, mingo, peterz, adrian.hunter,
alexander.shishkin, james.clark, jolsa, mark.rutland, dapeng1.mi,
thomas.falcon, tianyou.li, wangyang.guo, linux-perf-users,
linux-kernel
On Mon, Aug 17, 2026 at 3:48 PM Namhyung Kim <namhyung@kernel.org> wrote:
>
> Hello,
>
> On Mon, Aug 17, 2026 at 01:53:46PM -0700, Ian Rogers wrote:
> > On Mon, Aug 17, 2026 at 2:40 AM Jiebin Sun <jiebin.sun@intel.com> wrote:
> > >
> > > Add the initial common model for the c2c function view: model state and
> > > small helpers shared by the hierarchy construction and formatting added
> > > in later patches.
> > >
> > > Build the model from util/ so it remains independent of the TUI and
> > > command-private symbols.
> > >
> > > Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
> > > Cc: Adrian Hunter <adrian.hunter@intel.com>
> > > Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
> > > Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
> > > Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
> > > Cc: Ian Rogers <irogers@google.com>
> > > Cc: Ingo Molnar <mingo@redhat.com>
> > > Cc: James Clark <james.clark@linaro.org>
> > > Cc: Jiri Olsa <jolsa@kernel.org>
> > > Cc: Mark Rutland <mark.rutland@arm.com>
> > > Cc: Namhyung Kim <namhyung@kernel.org>
> > > Cc: Peter Zijlstra <peterz@infradead.org>
> > > Cc: Thomas Falcon <thomas.falcon@intel.com>
> > > Reviewed-by: Tianyou Li <tianyou.li@intel.com>
> > > Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
> > > ---
> > > tools/perf/util/Build | 1 +
> > > tools/perf/util/c2c-function.c | 66 ++++++++++++++++++++++++++++++++++
> > > 2 files changed, 67 insertions(+)
> > > create mode 100644 tools/perf/util/c2c-function.c
> > >
> > > diff --git a/tools/perf/util/Build b/tools/perf/util/Build
> > > index 1dfd92cbe3b7..b26a0b1ddfa3 100644
> > > --- a/tools/perf/util/Build
> > > +++ b/tools/perf/util/Build
> > > @@ -12,6 +12,7 @@ perf-util-y += block-info.o
> > > perf-util-y += block-range.o
> > > perf-util-y += build-id.o
> > > perf-util-y += c2c.o
> > > +perf-util-y += c2c-function.o
> > > perf-util-y += cacheline.o
> > > perf-util-$(CONFIG_LIBCAPSTONE) += capstone.o
> > > perf-util-y += config.o
> > > diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c
> > > new file mode 100644
> > > index 000000000000..ca82425a28dc
> > > --- /dev/null
> > > +++ b/tools/perf/util/c2c-function.c
> > > @@ -0,0 +1,66 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * C2C function model - function-level cacheline sharing analysis
> > > + *
> > > + * Displays a 3-level hierarchy showing which functions share cachelines:
> > > + * Level 1: Read-side functions sorted by Cycles % (estimated load cycles)
> > > + * Level 2: Functions sampled writing the shared lines read by level 1
> > > + * Level 3: The specific cachelines where the two functions contend
> > > + *
> > > + * Builds the hierarchy from the existing cacheline histograms
> > > + * (c2c_hist_entry->hists), reusing the shared c2c data structures.
> > > + */
> > > +
> > > +#include <errno.h>
> > > +#include <inttypes.h>
> > > +#include <stdlib.h>
> > > +#include <string.h>
> > > +#include <tools/libc_compat.h> /* reallocarray */
> > > +#include <linux/list.h>
> > > +#include <linux/rbtree.h>
> > > +#include <linux/zalloc.h>
> > > +
> > > +#include "addr_location.h"
> > > +#include "c2c.h"
> > > +#include "cacheline.h"
> > > +#include "hist.h"
> > > +#include "map.h"
> > > +#include "mem-events.h"
> > > +#include "mem-info.h"
> > > +#include "sort.h"
> > > +#include "symbol.h"
> > > +#include "thread.h"
> >
> > nit: the number of #includes is somewhat generous here. I presume
> > later patches will require these includes. To avoid everything
> > depending on everything else it would be nice to use forward
> > declarations when possible. For example, if the only reason for
> > including the header file was to use a struct's name where it is
> > passed as a pointer in a function declaration, ie in header files
> > prefer:
> >
> > struct map;
> > int foo(struct map *m);
> >
> > over
> >
> > #include "map.h"
> > int foo(struct map *m);
>
> I think they are actually used in the later patches in this series.
Ah. I misread and thought they were being added to the header file. My
mistake and I guess it is okay to add unused header file #includes in
the same way as the patches introduce initially unused functions.
Reviewed-by: Ian Rogers <irogers@google.com>
Thanks,
Ian
> >
> > > +
> > > +struct c2c_function_model {
> > > + struct c2c_hists function_hists;
> > > + /* Total estimated cycles across all level-1 entries. */
> > > + u64 total_cycles;
> > > + /* Source cacheline histograms; not owned here. */
> > > + struct c2c_hists *cl_hists;
> > > + /* --coalesce field list, used to require iaddr. */
> > > + const char *cl_sort;
> > > + /* Do not cap long symbol names. */
> > > + bool symbol_full;
> > > +};
> > > +
> > > +static struct c2c_function_model c2c_ext __maybe_unused;
> > > +
> > > +static inline __maybe_unused u64 c2c_hitm_count(const struct c2c_stats *stats)
> > > +{
> > > + return stats->tot_hitm;
> > > +}
> > > +
> > > +static inline __maybe_unused bool symbol_name_equal(struct symbol *a, struct symbol *b)
> > > +{
> > > + /* Two unknown symbols compare equal, matching cmp_null() in util/sort.c. */
> > > + if (!a || !b)
> > > + return a == b;
> > > + return arch__compare_symbol_names(a->name, b->name) == 0;
> >
> > Sashiko rightly flagged this as not being cross-platform compatible,
> > but this is a pre-existing issue that looks relatively easy to clean
> > up but only really impacts PowerPC and so is hard for me to test. I'll
> > try to do it anyway.
>
> Thanks for your review. Yep, I think it can be handled separately.
>
> Namhyung
>
> >
> > > +}
> > > +
> > > +static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
> > > +{
> > > + if (he->mem_info)
> > > + return mem_info__iaddr(he->mem_info)->addr;
> > > + return he->ip;
> > > +}
> > > --
> > > 2.52.0
> > >
^ permalink raw reply [flat|nested] 30+ messages in thread
end of thread, other threads:[~2026-08-17 22:54 UTC | newest]
Thread overview: 30+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-17 9:46 [PATCH v9 0/9] perf c2c: add a function view Jiebin Sun
2026-08-17 9:46 ` [PATCH v9 1/9] perf c2c: extract shared data structures into util/c2c.h Jiebin Sun
2026-08-17 9:59 ` sashiko-bot
2026-08-17 20:34 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 2/9] perf c2c: add function view model skeleton Jiebin Sun
2026-08-17 9:48 ` sashiko-bot
2026-08-17 20:53 ` Ian Rogers
2026-08-17 22:48 ` Namhyung Kim
2026-08-17 22:53 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 3/9] perf c2c: add column rendering for function view Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
2026-08-17 20:58 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 4/9] perf c2c: add HPP list parsing for function view columns Jiebin Sun
2026-08-17 9:50 ` sashiko-bot
2026-08-17 21:04 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 5/9] perf c2c: add function view stats merge and memory management Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
2026-08-17 21:05 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 6/9] perf c2c: add function view hierarchy entry creation Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
2026-08-17 21:06 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 7/9] perf c2c: build and finalize the function view hierarchy Jiebin Sun
2026-08-17 9:54 ` sashiko-bot
2026-08-17 21:11 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 8/9] perf c2c: add function view browser UI and cacheline detail Jiebin Sun
2026-08-17 9:51 ` sashiko-bot
2026-08-17 21:12 ` Ian Rogers
2026-08-17 9:46 ` [PATCH v9 9/9] perf c2c: document function view in perf-c2c man page Jiebin Sun
2026-08-17 9:49 ` sashiko-bot
2026-08-17 21:16 ` Ian Rogers
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox