tools/perf/Documentation/perf-c2c.txt | 33 + tools/perf/builtin-c2c.c | 132 +-- tools/perf/c2c.h | 148 +++ tools/perf/ui/browsers/Build | 1 + tools/perf/ui/browsers/c2c-function.c | 1562 +++++++++++++++++++++++++ 5 files changed, 1756 insertions(+), 120 deletions(-) create mode 100644 tools/perf/c2c.h create mode 100644 tools/perf/ui/browsers/c2c-function.c
This series adds a new "function view" to perf c2c report, on top of the
existing cacheline view. It does not change the cacheline view; it adds a
second, complementary way to look at the same cache-to-cache (C2C) data.
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/
Changes since v2
================
- perf c2c: add a c2c_ prefix to the functions exported from
builtin-c2c.c for the function view, so the shared symbols do not
pollute the global namespace: fmt_free() -> c2c_fmt_free() and
fmt_equal() -> c2c_fmt_equal(). Suggested by Namhyung.
- perf c2c: free the two top-level function-view hists with
hists__delete_all_entries() as well, so entries left in the
input/collapsed trees are not leaked.
- perf c2c: drop a redundant reset_dimensions() call, free the
registered formats on a mid-parse failure in the function-view HPP
list parser, and add a missing newline to a pr_err() message.
- Rebased onto the latest perf-tools-next.
Changes since v1
================
- Add the worked example (the cpupri_set/pull_rt_task walk-through) to
the perf-c2c man page, as suggested by Namhyung.
- Rebased onto the latest perf-tools-next.
- perf c2c: cache the level-1 total-stores sum (new child_stores field)
instead of walking the children on every comparison. total_stores_cmp()
is called during collapse/sort, so the previous O(children) walk was an
O(N*M log N) bottleneck on large profiles.
- perf c2c: free the function-view sub-hists with
hists__delete_all_entries() instead of hists__delete_entries(), so
entries still in the input/collapsed trees are not leaked, matching the
cleanup in builtin-c2c.c.
- perf c2c: distinguish -ENOMEM from "unknown dimension" in
get_function_format(), so an allocation failure is no longer
misreported as an "Invalid c2c function-view field".
- perf c2c: reword the iaddr_symbol_cmp() sort-order comment to drop the
ambiguous ascending/descending wording.
What it does
============
In the perf c2c TUI, press TAB in the cacheline view to switch to the
function view. It presents a 3-level hierarchy:
Level 1: primary functions, sorted by Cycles % (estimated load cycles:
HITM, peer-snoop and other-load cycles -- on systems whose
default display mode is peer, such as Arm64, the peer-snoop
component dominates)
Level 2: other functions that share cachelines with the level-1 function
Level 3: the specific shared cachelines for each function pair
Keys in the function view:
TAB/ESC/q return to the cacheline view
d show cacheline details for the selected entry
e / + expand / collapse the selected entry
? help
The cacheline view and the --stdio output are unchanged.
Example
=======
A level-1 function is expanded (press 'e') to reveal the functions it
shares cachelines with, and one of those is expanded again to reveal the
specific shared cachelines:
Shared Data Functions Table (27 entries, sorted on Cycles %)
Cycles Store
% count Code address Symbol Cacheline
----------------------------------------------------------------------
- 39.03% 541 - 0xffffffffa2fc5b08 - [k] cpupri_set
450 - 0xffffffffa2fa28a5 - [k] pull_rt_task
450 0xff2d0082809da080
Reading the three levels:
- Level 1: cpupri_set is the top contended function, accounting for
39.03% of the estimated load cycles. The table is sorted by this
Cycles % column.
- Level 2: expanding cpupri_set (press 'e') lists the functions it
shares cachelines with, sorted by store count. Here pull_rt_task is
the contending function, with 450 stores into the shared data.
- Level 3: expanding pull_rt_task lists the specific cachelines the two
functions contend over -- in this case the single cacheline at
0xff2d0082809da080.
The view reads top-down as "cpupri_set is hottest; it shares data with
pull_rt_task; the contention is on cacheline ...da080" -- the false-
sharing chain that the cacheline view otherwise makes you reconstruct by
hand.
Implementation
==============
The function view is built as a separate hist_browser in
tools/perf/ui/browsers/c2c-function.c. Shared types and helpers used by
both views are factored out of builtin-c2c.c into a new c2c.h. The
hierarchy is constructed from the existing cacheline histograms into a
dedicated set of hists, keyed by (symbol, instruction address), and
rendered with custom column formatters.
The series is split into 14 small, self-contained patches so each step
can be reviewed and builds on its own.
Testing
=======
- Each of the 14 commits builds individually and as a full series.
- perf c2c report --stdio (cacheline view) output is unchanged versus
the baseline: identical trace-event totals, shared-cacheline counts,
and HITM tallies.
- The function view was exercised on c2c recordings; the level-1
ordering and the level-2/3 sharing breakdown match the underlying
cacheline data.
Jiebin Sun (14):
perf c2c: extract shared data structures into c2c.h
perf c2c: add function view browser skeleton
perf c2c: add function view type definitions and helpers
perf c2c: add column format infrastructure for function view
perf c2c: add column entry functions for function view
perf c2c: add comparison functions for function view sorting
perf c2c: add dimension definitions and format creation
perf c2c: add HPP list parsing for function view histograms
perf c2c: add stats merging and memory management helpers
perf c2c: add hierarchy entry creation and lookup functions
perf c2c: add function view hierarchy builder
perf c2c: add function view browser UI
perf c2c: add TAB key to switch to function view
perf c2c: document function view in perf-c2c man page
tools/perf/Documentation/perf-c2c.txt | 33 +
tools/perf/builtin-c2c.c | 132 +--
tools/perf/c2c.h | 148 +++
tools/perf/ui/browsers/Build | 1 +
tools/perf/ui/browsers/c2c-function.c | 1562 +++++++++++++++++++++++++
5 files changed, 1756 insertions(+), 120 deletions(-)
create mode 100644 tools/perf/c2c.h
create mode 100644 tools/perf/ui/browsers/c2c-function.c
base-commit: 3f7909fa921e38b7aba8566b03ca5e0754a8fa8b
--
2.52.0
On Fri, Jul 17, 2026 at 10:05:16AM +0800, Jiebin Sun wrote:
> This series adds a new "function view" to perf c2c report, on top of the
> existing cacheline view. It does not change the cacheline view; it adds a
> second, complementary way to look at the same cache-to-cache (C2C) data.
>
> 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/
>
> Changes since v2
> ================
>
> - perf c2c: add a c2c_ prefix to the functions exported from
> builtin-c2c.c for the function view, so the shared symbols do not
> pollute the global namespace: fmt_free() -> c2c_fmt_free() and
> fmt_equal() -> c2c_fmt_equal(). Suggested by Namhyung.
>
> - perf c2c: free the two top-level function-view hists with
> hists__delete_all_entries() as well, so entries left in the
> input/collapsed trees are not leaked.
>
> - perf c2c: drop a redundant reset_dimensions() call, free the
> registered formats on a mid-parse failure in the function-view HPP
> list parser, and add a missing newline to a pr_err() message.
>
> - Rebased onto the latest perf-tools-next.
>
> Changes since v1
> ================
>
> - Add the worked example (the cpupri_set/pull_rt_task walk-through) to
> the perf-c2c man page, as suggested by Namhyung.
>
> - Rebased onto the latest perf-tools-next.
>
> - perf c2c: cache the level-1 total-stores sum (new child_stores field)
> instead of walking the children on every comparison. total_stores_cmp()
> is called during collapse/sort, so the previous O(children) walk was an
> O(N*M log N) bottleneck on large profiles.
>
> - perf c2c: free the function-view sub-hists with
> hists__delete_all_entries() instead of hists__delete_entries(), so
> entries still in the input/collapsed trees are not leaked, matching the
> cleanup in builtin-c2c.c.
>
> - perf c2c: distinguish -ENOMEM from "unknown dimension" in
> get_function_format(), so an allocation failure is no longer
> misreported as an "Invalid c2c function-view field".
>
> - perf c2c: reword the iaddr_symbol_cmp() sort-order comment to drop the
> ambiguous ascending/descending wording.
>
> What it does
> ============
>
> In the perf c2c TUI, press TAB in the cacheline view to switch to the
> function view. It presents a 3-level hierarchy:
>
> Level 1: primary functions, sorted by Cycles % (estimated load cycles:
> HITM, peer-snoop and other-load cycles -- on systems whose
> default display mode is peer, such as Arm64, the peer-snoop
> component dominates)
> Level 2: other functions that share cachelines with the level-1 function
> Level 3: the specific shared cachelines for each function pair
>
> Keys in the function view:
> TAB/ESC/q return to the cacheline view
> d show cacheline details for the selected entry
> e / + expand / collapse the selected entry
> ? help
>
> The cacheline view and the --stdio output are unchanged.
>
> Example
> =======
>
> A level-1 function is expanded (press 'e') to reveal the functions it
> shares cachelines with, and one of those is expanded again to reveal the
> specific shared cachelines:
>
> Shared Data Functions Table (27 entries, sorted on Cycles %)
> Cycles Store
> % count Code address Symbol Cacheline
> ----------------------------------------------------------------------
> - 39.03% 541 - 0xffffffffa2fc5b08 - [k] cpupri_set
> 450 - 0xffffffffa2fa28a5 - [k] pull_rt_task
> 450 0xff2d0082809da080
I think it's more like "code-address" view rather than "function" view.
How about organizing the output something like below?
L1: function
L2: cache line
L3: shared functions
or
L1: function
L2: code address
L3: cache line
L4: shared functions
A function can have multiple entries with different code addresses and
cache lines. They can be grouped together for better analysis.
Also, I think you'd better to indent output for lower level entries to
clearly separate entries from others like in the normal perf report
hierarchy view. Something like this.
Shared Data Functions Table (27 entries, sorted on Cycles %)
Cycles Store
% count Code address Symbol Cacheline
----------------------------------------------------------------------
- 39.03% 541 - 0xffffffffa2fc5b08 - [k] cpupri_set
450 - 0xffffffffa2fa28a5 - [k] pull_rt_task <<<-- here
450 0xff2d0082809da080
... + [k] other_func
Thanks,
Namhyung
>
> Reading the three levels:
>
> - Level 1: cpupri_set is the top contended function, accounting for
> 39.03% of the estimated load cycles. The table is sorted by this
> Cycles % column.
> - Level 2: expanding cpupri_set (press 'e') lists the functions it
> shares cachelines with, sorted by store count. Here pull_rt_task is
> the contending function, with 450 stores into the shared data.
> - Level 3: expanding pull_rt_task lists the specific cachelines the two
> functions contend over -- in this case the single cacheline at
> 0xff2d0082809da080.
>
> The view reads top-down as "cpupri_set is hottest; it shares data with
> pull_rt_task; the contention is on cacheline ...da080" -- the false-
> sharing chain that the cacheline view otherwise makes you reconstruct by
> hand.
>
> Implementation
> ==============
>
> The function view is built as a separate hist_browser in
> tools/perf/ui/browsers/c2c-function.c. Shared types and helpers used by
> both views are factored out of builtin-c2c.c into a new c2c.h. The
> hierarchy is constructed from the existing cacheline histograms into a
> dedicated set of hists, keyed by (symbol, instruction address), and
> rendered with custom column formatters.
>
> The series is split into 14 small, self-contained patches so each step
> can be reviewed and builds on its own.
>
> Testing
> =======
>
> - Each of the 14 commits builds individually and as a full series.
> - perf c2c report --stdio (cacheline view) output is unchanged versus
> the baseline: identical trace-event totals, shared-cacheline counts,
> and HITM tallies.
> - The function view was exercised on c2c recordings; the level-1
> ordering and the level-2/3 sharing breakdown match the underlying
> cacheline data.
>
> Jiebin Sun (14):
> perf c2c: extract shared data structures into c2c.h
> perf c2c: add function view browser skeleton
> perf c2c: add function view type definitions and helpers
> perf c2c: add column format infrastructure for function view
> perf c2c: add column entry functions for function view
> perf c2c: add comparison functions for function view sorting
> perf c2c: add dimension definitions and format creation
> perf c2c: add HPP list parsing for function view histograms
> perf c2c: add stats merging and memory management helpers
> perf c2c: add hierarchy entry creation and lookup functions
> perf c2c: add function view hierarchy builder
> perf c2c: add function view browser UI
> perf c2c: add TAB key to switch to function view
> perf c2c: document function view in perf-c2c man page
>
> tools/perf/Documentation/perf-c2c.txt | 33 +
> tools/perf/builtin-c2c.c | 132 +--
> tools/perf/c2c.h | 148 +++
> tools/perf/ui/browsers/Build | 1 +
> tools/perf/ui/browsers/c2c-function.c | 1562 +++++++++++++++++++++++++
> 5 files changed, 1756 insertions(+), 120 deletions(-)
> create mode 100644 tools/perf/c2c.h
> create mode 100644 tools/perf/ui/browsers/c2c-function.c
>
>
> base-commit: 3f7909fa921e38b7aba8566b03ca5e0754a8fa8b
> --
> 2.52.0
>
Hi Namhyung,
Thanks a lot for the detailed suggestions -- agreed on both points, and I
wanted to get your thoughts on the reworked layout before sending the next
version.
> I think it's more like "code-address" view rather than "function" view.
> How about organizing the output something like below?
>
> L1: function
> L2: cache line
> L3: shared functions
You're right that keying level 1 on the code address made it read like a
"code-address view". I plan to rework it so every level that names a
function aggregates all of that function's code addresses into a single
entry, together with the per-level indentation you described.
For the lower levels I first tried the cacheline-then-functions layout you
suggested:
L1: function
L2: cache line
L3: sharing functions
but on real data we hit one problem: when the level-1 function touches many
cache lines, the same contending function shows up under many of those
level-2 cache lines. To find out which function modifies the line the most
-- the one most worth fixing -- we then have to aggregate that function's
writes across all those cache lines ourselves, which is the same manual
work the cache line view already leaves to us.
So I am considering putting the contending function at level 2 and the
cache line at level 3 instead:
L1: function (reads the line and pays the HITM penalty)
L2: contending function (writes/modifies the line, causing the HITM)
L3: the shared cache lines
For example, expanding dequeue_pushable_task and its writers:
Cycles Store
% count Function / Contending function / Cache line
------------------------------------------------------------
- 16.54% 419 - [k] dequeue_pushable_task
145 - [k] pull_rt_task
145 0xff2d0082809da080
139 - [k] enqueue_pushable_task
70 0xff2d00a2071f9640
69 0xff2d0082809da000
133 - [k] cpupri_set
133 0xff2d0082809da080
Here enqueue_pushable_task contends over two cache lines, but it stays a
single level-2 row with its total (139) shown, and the individual cache
lines are underneath -- so the top writers are ranked directly, with no
manual summing.
The key difference from the earlier version is that the code addresses and
function offsets are no longer shown at level 1 and level 2. Those made the
view read like a code-address view; instead the main view keeps only the
concise, at-a-glance information (which function pays, which function it
contends with, and the cache lines involved), and the detailed code
addresses / offsets stay in the per-cacheline detail view ('d').
Once we settle on the layout, I'll send the updated version for review.
Thanks,
Jiebin
Hello,
On Mon, Jul 20, 2026 at 04:39:24PM +0800, Jiebin Sun wrote:
> Hi Namhyung,
>
> Thanks a lot for the detailed suggestions -- agreed on both points, and I
> wanted to get your thoughts on the reworked layout before sending the next
> version.
>
> > I think it's more like "code-address" view rather than "function" view.
> > How about organizing the output something like below?
> >
> > L1: function
> > L2: cache line
> > L3: shared functions
>
> You're right that keying level 1 on the code address made it read like a
> "code-address view". I plan to rework it so every level that names a
> function aggregates all of that function's code addresses into a single
> entry, together with the per-level indentation you described.
>
> For the lower levels I first tried the cacheline-then-functions layout you
> suggested:
>
> L1: function
> L2: cache line
> L3: sharing functions
>
> but on real data we hit one problem: when the level-1 function touches many
> cache lines, the same contending function shows up under many of those
> level-2 cache lines. To find out which function modifies the line the most
> -- the one most worth fixing -- we then have to aggregate that function's
> writes across all those cache lines ourselves, which is the same manual
> work the cache line view already leaves to us.
>
> So I am considering putting the contending function at level 2 and the
> cache line at level 3 instead:
>
> L1: function (reads the line and pays the HITM penalty)
> L2: contending function (writes/modifies the line, causing the HITM)
> L3: the shared cache lines
>
> For example, expanding dequeue_pushable_task and its writers:
>
> Cycles Store
> % count Function / Contending function / Cache line
> ------------------------------------------------------------
> - 16.54% 419 - [k] dequeue_pushable_task
> 145 - [k] pull_rt_task
> 145 0xff2d0082809da080
> 139 - [k] enqueue_pushable_task
> 70 0xff2d00a2071f9640
> 69 0xff2d0082809da000
> 133 - [k] cpupri_set
> 133 0xff2d0082809da080
>
> Here enqueue_pushable_task contends over two cache lines, but it stays a
> single level-2 row with its total (139) shown, and the individual cache
> lines are underneath -- so the top writers are ranked directly, with no
> manual summing.
>
> The key difference from the earlier version is that the code addresses and
> function offsets are no longer shown at level 1 and level 2. Those made the
> view read like a code-address view; instead the main view keeps only the
> concise, at-a-glance information (which function pays, which function it
> contends with, and the cache lines involved), and the detailed code
> addresses / offsets stay in the per-cacheline detail view ('d').
>
> Once we settle on the layout, I'll send the updated version for review.
Thanks for the explanation and it looks good!
Maybe you can add a shortcut to the cacheline view on the given item in
a follow up work.
Thanks,
Namhyung
Hi Namhyung,
Thanks a lot for the review and for confirming the layout.
I've just posted v4 with the reworked hierarchy:
Level 1: the read-side function (pays the estimated HITM/peer
load-cycle cost)
Level 2: the contending function that writes the shared lines
Level 3: the specific shared cachelines
Every level now shows functions and cachelines only, with per-level
indentation; the code addresses and offsets are no longer shown in the
main view -- they remain available in the cacheline detail view. The
series is also reorganized into 9 logically self-contained patches.
> ... add a shortcut to the cacheline view on the given item.
The function view already supports this for level-3 cacheline entries:
pressing 'd' opens the corresponding cacheline detail view.
Thanks,
Jiebin
This series adds a new "function view" to perf c2c report, on top of the
existing cacheline view. It does not change the cacheline view; it adds a
second, complementary way to look at the same cache-to-cache (C2C) data.
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/
Changes since v3
================
Reworked the function view into a true function-centric hierarchy, as
discussed with Namhyung:
- Level 1 is now the read-side function itself, aggregating all of its
code addresses into a single entry (v3 keyed level 1 on the code
address, which read like a "code-address view"). Suggested by
Namhyung.
- Level 2 is the contending function that writes the shared lines
(causing the level-1 function's HITMs), aggregated across all the
cachelines it contends over and sorted by store count. Level 3 is
the specific cachelines. This lets a contending pair show its total
on one row, instead of repeating a writer under every cacheline and
leaving the reader to sum by hand.
- Every level shows only functions and cachelines; code addresses and
offsets are no longer shown in the main view. They remain available
in the per-cacheline detail view ('d').
- Lower-level entries are indented per hierarchy level, like the normal
perf report hierarchy view. Suggested by Namhyung.
- Cachelines with no writing function, and functions left with no
contending writer, are pruned so the view shows only real contention.
- Reorganized the series from 14 mechanical patches into 9 logically
self-contained ones (skeleton, column rendering, HPP parsing, stats
merge, entry creation, build/finalize, browser UI, doc).
- Updated the perf-c2c man page example to the new layout.
- Rebased onto the latest perf-tools-next.
Changes since v2
================
- perf c2c: add a c2c_ prefix to the functions exported from
builtin-c2c.c (fmt_free() -> c2c_fmt_free(), fmt_equal() ->
c2c_fmt_equal()), so the shared symbols do not pollute the global
namespace. Suggested by Namhyung.
Changes since v1
================
- Add the worked man-page example, as suggested by Namhyung.
What it does
============
In the perf c2c TUI, press TAB in the cacheline view to switch to the
function view. It presents a 3-level hierarchy of the same data,
organized around functions rather than cachelines:
Level 1: the read-side function, sorted by Cycles % (estimated load
cycles: HITM, peer-snoop and other-load cycles -- on systems
whose default display mode is peer, such as Arm64, the
peer-snoop component dominates)
Level 2: the functions that write the shared lines, causing the
level-1 function's HITMs, sorted by store count
Level 3: the specific cachelines where the two functions contend
Every level shows functions and cachelines only; code addresses are not
shown in the main view -- they remain available in the per-cacheline
detail view ('d'). Each function aggregates all of its code addresses
into a single entry, and a level-2 writer aggregates all of the
cachelines it contends over, so a contending pair is a single row with
its total shown.
Keys in the function view:
TAB/ESC/q return to the cacheline view
d show cacheline details for the selected entry
e / + expand / collapse the selected entry
? help
The cacheline view and the --stdio output are unchanged.
Example
=======
A level-1 function is expanded (press 'e') to reveal the functions that
write the lines it reads, and one of those is expanded again to reveal
the specific shared cachelines:
Shared Data Functions Table (18 entries, sorted on Cycles %)
Cycles Store
% count Function / Contending function / Cacheline
----------------------------------------------------------------------
- 16.54% 419 - [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 the read-side HITM penalty (16.54% of the
estimated load cycles). It is contended by pull_rt_task and
enqueue_pushable_task, which write the shared lines; enqueue_pushable_task
contends over two cachelines but stays a single row with its total (139)
shown, and the individual cachelines are underneath. This is the
false-sharing chain that the cacheline view otherwise leaves to be
reconstructed by hand.
Implementation
==============
The function view is a separate hist_browser in
tools/perf/ui/browsers/c2c-function.c. Shared types and helpers used by
both views are factored out of builtin-c2c.c into a new c2c.h. The
hierarchy is constructed from the existing cacheline histograms into a
dedicated set of hists and rendered with custom column formatters.
Testing
=======
- Each of the 9 commits builds individually and as a full series.
- perf c2c report --stdio (cacheline view) output is unchanged versus
the baseline.
- The function view was exercised on c2c recordings; the level-1
ordering and the level-2/3 sharing breakdown match the underlying
cacheline data.
Jiebin Sun (9):
perf c2c: extract shared data structures into c2c.h
perf c2c: add function view browser 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 | 46 +
tools/perf/builtin-c2c.c | 132 +-
tools/perf/c2c.h | 154 +++
tools/perf/ui/browsers/Build | 1 +
tools/perf/ui/browsers/c2c-function.c | 1789 +++++++++++++++++++++++++
5 files changed, 2002 insertions(+), 120 deletions(-)
create mode 100644 tools/perf/c2c.h
create mode 100644 tools/perf/ui/browsers/c2c-function.c
base-commit: 87ec3437f37b9fe44c524ba967cb12e78de06f15
--
2.52.0
Move c2c_hists, compute_stats, c2c_hist_entry, and perf_c2c structure
definitions from builtin-c2c.c into a new shared header c2c.h. This
allows the upcoming function view browser (c2c-function.c) to reuse these
types.
Make the global perf_c2c instance 'c2c' non-static and export
perf_c2c__browse_cacheline() so they can be accessed from the new
function view module.
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 | 128 ++------------------------------
tools/perf/c2c.h | 153 +++++++++++++++++++++++++++++++++++++++
2 files changed, 161 insertions(+), 120 deletions(-)
create mode 100644 tools/perf/c2c.h
diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c
index c9584dbedf77..f1dd9d8699cb 100644
--- a/tools/perf/builtin-c2c.c
+++ b/tools/perf/builtin-c2c.c
@@ -38,6 +38,7 @@
#include "mem-events.h"
#include "mem-info.h"
#include "mem2node.h"
+#include "c2c.h"
#include "pmus.h"
#include "session.h"
#include "sort.h"
@@ -52,75 +53,9 @@
#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 {
- struct perf_tool tool;
- struct c2c_hists hists;
- struct mem2node mem2node;
-
- unsigned long **nodes;
- int nodes_cnt;
- int cpus_cnt;
- int *cpu2node;
- int node_info;
-
- bool show_src;
- bool show_all;
- bool use_stdio;
- bool stats_only;
- bool symbol_full;
- bool stitch_lbr;
-
- /* Shared cache line stats */
- struct c2c_stats shared_clines_stats;
- int shared_clines;
-
- int display;
-
- const char *coalesce;
- char *cl_sort;
- char *cl_resort;
- char *cl_output;
-};
-
enum {
DISPLAY_LCL_HITM,
DISPLAY_RMT_HITM,
@@ -141,9 +76,9 @@ static const struct option c2c_options[] = {
OPT_END()
};
-static struct perf_c2c c2c;
+struct perf_c2c c2c;
-static void *c2c_he_zalloc(size_t size)
+void *c2c_he_zalloc(size_t size)
{
struct c2c_hist_entry *c2c_he;
@@ -458,36 +393,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;
@@ -1389,23 +1294,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] = { \
@@ -1928,7 +1816,7 @@ static struct c2c_dimension *dimensions[] = {
NULL,
};
-static void fmt_free(struct perf_hpp_fmt *fmt)
+void c2c_fmt_free(struct perf_hpp_fmt *fmt)
{
struct c2c_fmt *c2c_fmt;
@@ -1936,7 +1824,7 @@ static void fmt_free(struct perf_hpp_fmt *fmt)
free(c2c_fmt);
}
-static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
+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);
@@ -2021,8 +1909,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;
}
@@ -2710,7 +2598,7 @@ c2c_cacheline_browser__new(struct hists *hists, struct hist_entry *he)
return browser;
}
-static int perf_c2c__browse_cacheline(struct hist_entry *he)
+int perf_c2c__browse_cacheline(struct hist_entry *he)
{
struct c2c_hist_entry *c2c_he;
struct c2c_hists *c2c_hists;
diff --git a/tools/perf/c2c.h b/tools/perf/c2c.h
new file mode 100644
index 000000000000..432d5f0b28f1
--- /dev/null
+++ b/tools/perf/c2c.h
@@ -0,0 +1,153 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _PERF_C2C_H_
+#define _PERF_C2C_H_ 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <linux/types.h>
+#include "util/stat.h"
+#include "util/hist.h"
+#include "util/mem-events.h"
+#include "util/mem2node.h"
+#include "util/tool.h"
+
+struct sort_entry;
+
+struct compute_stats {
+ struct stats lcl_hitm;
+ struct stats rmt_hitm;
+ struct stats lcl_peer;
+ struct stats rmt_peer;
+ struct stats load;
+};
+
+struct c2c_hists {
+ struct hists hists;
+ struct perf_hpp_list list;
+ struct c2c_stats stats;
+};
+
+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;
+
+ /*
+ * Function view only: cached sum of the level-2 children's store
+ * counts, accumulated while the hierarchy is built. Lets the
+ * total-stores column and its sort comparator avoid an O(children)
+ * walk on every access. Unused by the cacheline view.
+ */
+ u64 child_stores;
+
+ /*
+ * must be at the end,
+ * because of its callchain dynamic entry
+ */
+ struct hist_entry he;
+};
+
+struct perf_c2c {
+ struct perf_tool tool;
+ struct c2c_hists hists;
+ struct mem2node mem2node;
+
+ unsigned long **nodes;
+ int nodes_cnt;
+ int cpus_cnt;
+ int *cpu2node;
+ int node_info;
+
+ bool show_src;
+ bool show_all;
+ bool use_stdio;
+ bool stats_only;
+ bool symbol_full;
+ bool stitch_lbr;
+
+ /* Shared cache line stats */
+ struct c2c_stats shared_clines_stats;
+ int shared_clines;
+
+ int display;
+
+ const char *coalesce;
+ char *cl_sort;
+ char *cl_resort;
+ char *cl_output;
+};
+
+extern struct perf_c2c c2c;
+
+#define C2C_HEADER_MAX 2
+#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, \
+ }, \
+ }
+
+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;
+};
+
+void *c2c_he_zalloc(size_t size);
+void c2c_fmt_free(struct perf_hpp_fmt *fmt);
+bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b);
+
+/*
+ * The TUI browsers are only built with SLANG support. Their sole callers
+ * live inside HAVE_SLANG_SUPPORT-guarded code in builtin-c2c.c (stdio mode
+ * forces use_browser=0), so no #else stub is needed -- matching the rest of
+ * perf's TUI entry points.
+ */
+#ifdef HAVE_SLANG_SUPPORT
+int perf_c2c__browse_cacheline(struct hist_entry *he);
+#endif
+
+#endif /* _PERF_C2C_H_ */
--
2.52.0
Add the skeleton of the c2c function view: a new TUI browser in
tools/perf/ui/browsers/c2c-function.c reached by pressing TAB in the
cacheline view. This commit wires up the entry point (a stub that will be
filled in by later patches), registers the file in the build, declares
perf_c2c__browse_function_view() in c2c.h, and adds the TAB key handler
and help text to the cacheline browser.
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 | 4 ++
tools/perf/c2c.h | 1 +
tools/perf/ui/browsers/Build | 1 +
tools/perf/ui/browsers/c2c-function.c | 79 +++++++++++++++++++++++++++
4 files changed, 85 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 f1dd9d8699cb..23c58eba415a 100644
--- a/tools/perf/builtin-c2c.c
+++ b/tools/perf/builtin-c2c.c
@@ -2693,6 +2693,7 @@ static int perf_c2c__hists_browse(struct hists *hists)
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);
@@ -2714,6 +2715,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(hists);
+ break;
case '?':
ui_browser__help_window(&browser->b, help);
break;
diff --git a/tools/perf/c2c.h b/tools/perf/c2c.h
index 432d5f0b28f1..8d0871d9556b 100644
--- a/tools/perf/c2c.h
+++ b/tools/perf/c2c.h
@@ -148,6 +148,7 @@ bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b);
*/
#ifdef HAVE_SLANG_SUPPORT
int perf_c2c__browse_cacheline(struct hist_entry *he);
+int perf_c2c__browse_function_view(struct hists *hists);
#endif
#endif /* _PERF_C2C_H_ */
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..3f6822f61600
--- /dev/null
+++ b/tools/perf/ui/browsers/c2c-function.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * C2C Function Browser - 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 that write the shared lines, causing the level-1 HITMs
+ * 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 <tools/libc_compat.h> /* reallocarray */
+#include <string.h>
+#include <asm/bug.h>
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <linux/zalloc.h>
+
+#include "../browser.h"
+#include "../helpline.h"
+#include "../keysyms.h"
+#include "../libslang.h"
+#include "../ui.h"
+#include "../../util/addr_location.h"
+#include "../../util/cacheline.h"
+#include "../../util/debug.h"
+#include "../../util/hist.h"
+#include "../../util/map.h"
+#include "../../util/mem-events.h"
+#include "../../util/mem-info.h"
+#include "../../util/sort.h"
+#include "../../util/symbol.h"
+#include "../../util/thread.h"
+#include "../../c2c.h"
+#include "hists.h"
+
+
+struct perf_c2c_ext {
+ struct c2c_hists function_hists;
+ /* Cached across all level-1 entries; 0 means "not yet computed". */
+ u64 total_cycles;
+};
+
+static struct perf_c2c_ext c2c_ext __maybe_unused;
+
+struct c2c_function_browser {
+ struct hist_browser hb;
+};
+
+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;
+}
+
+int perf_c2c__browse_function_view(struct hists *hists __maybe_unused)
+{
+ ui__warning("C2C function view is not implemented yet.\n");
+ return 0;
+}
--
2.52.0
Add the function view's column renderers: the Cycles %, Store count and
the single indented identity column (function / cacheline) with per-level
indentation, the width/header helpers, the per-function estimated-cycles
computation, and the dimension table tying each column to its renderer and
comparator.
The HPP list parsing that turns a column string into these dimensions is
added by the next patch; symbols consumed only later are __maybe_unused
for now.
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/ui/browsers/c2c-function.c | 335 ++++++++++++++++++++++++++
1 file changed, 335 insertions(+)
diff --git a/tools/perf/ui/browsers/c2c-function.c b/tools/perf/ui/browsers/c2c-function.c
index 3f6822f61600..4eda97894d1d 100644
--- a/tools/perf/ui/browsers/c2c-function.c
+++ b/tools/perf/ui/browsers/c2c-function.c
@@ -72,6 +72,341 @@ static inline __maybe_unused u64 hist_entry__iaddr(struct hist_entry *he)
return he->ip;
}
+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, but never below
+ * what a level-3 cacheline address needs (deepest indent + folded sign
+ * + "0x" + 16 hex digits), so the address is not truncated when the
+ * level-1 function names happen to be short.
+ */
+ if (!c2c.symbol_full && width > SYMBOL_WIDTH) {
+ int cl_min = 2 * C2C_FUNC_INDENT + C2C_FUNC_FOLD_WIDTH + 2 + 16;
+
+ width = max(SYMBOL_WIDTH, cl_min);
+ }
+
+ 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 == NULL)
+ 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 hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
+ struct c2c_hist_entry *c2c_he = container_of(he, struct c2c_hist_entry, he);
+
+ total += c2c_hist_entry__cycles(c2c_he);
+ }
+ return total;
+}
+
+/*
+ * Sum of the writer store counts under a level-1 function or level-2 writing
+ * function. Read from the cache populated by the hierarchy builder, so this is
+ * O(1) and safe to call from the sort comparator.
+ */
+static u64 hist_entry__child_stores(struct hist_entry *he)
+{
+ struct c2c_hist_entry *c2c_he = container_of(he, struct c2c_hist_entry, he);
+
+ return c2c_he->child_stores;
+}
+
+/*
+ * Store count shown in the column: the level-3 cacheline leaves show the store
+ * count on that line; the level-1 function and level-2 writing function show
+ * the sum of their writer descendants.
+ */
+static u64 hist_entry__displayed_stores(struct hist_entry *he)
+{
+ struct c2c_hist_entry *c2c_he = container_of(he, struct c2c_hist_entry, he);
+
+ return hist_entry__is_cacheline(he) ? (u64)c2c_he->stats.store :
+ hist_entry__child_stores(he);
+}
+
+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 pure function view -- no code addresses:
+ * 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 = min_t(size_t, hpp->size - ret, text_width + 1);
+ int len;
+
+ len = sort_sym.se_snprintf(he, hpp->buf + ret, cell_size,
+ text_width);
+ 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,
+};
+
int perf_c2c__browse_function_view(struct hists *hists __maybe_unused)
{
ui__warning("C2C function view is not implemented yet.\n");
--
2.52.0
Add the parsing that turns an output/sort column string into the function
view's hpp_list: the dimension lookup, the sort-entry comparator wrappers,
the c2c_fmt allocation, and the init/reinit entry points used by the
hierarchy builder.
Make perf_hpp__setup_output_field() register appended sort keys on the
hpp_list passed by its caller. It previously used the global-list wrapper,
which left a local list without output fields and linked its formats into
the global perf_hpp_list instead.
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/ui/browsers/c2c-function.c | 268 +++++++++++++++++++++++++-
1 file changed, 266 insertions(+), 2 deletions(-)
diff --git a/tools/perf/ui/browsers/c2c-function.c b/tools/perf/ui/browsers/c2c-function.c
index 4eda97894d1d..21e156bbbef3 100644
--- a/tools/perf/ui/browsers/c2c-function.c
+++ b/tools/perf/ui/browsers/c2c-function.c
@@ -138,7 +138,7 @@ 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,
+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;
@@ -400,13 +400,277 @@ 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
+ * (caller should fall back to the generic field);
+ * -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);
+ INIT_LIST_HEAD(&c2c_hists->list.sorts);
+
+ /* 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);
+}
+
int perf_c2c__browse_function_view(struct hists *hists __maybe_unused)
{
ui__warning("C2C function view is not implemented yet.\n");
--
2.52.0
Add the per-entry stats/cstats aggregation helpers, the borrowed-reference
teardown for hierarchy children (which frees only the owned resources and
never the borrowed thread/ms), and the pruning of cachelines/functions
with no contention.
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/ui/browsers/c2c-function.c | 196 ++++++++++++++++++++++++++
1 file changed, 196 insertions(+)
diff --git a/tools/perf/ui/browsers/c2c-function.c b/tools/perf/ui/browsers/c2c-function.c
index 21e156bbbef3..1bddc6bdc073 100644
--- a/tools/perf/ui/browsers/c2c-function.c
+++ b/tools/perf/ui/browsers/c2c-function.c
@@ -671,6 +671,202 @@ c2c_function_hists__reinit(struct c2c_hists *c2c_hists,
return function_hpp_list__parse(&c2c_hists->list, output, sort, env);
}
+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);
+
+ he->stat.nr_events += nr_events;
+ he->stat.period += nr_events;
+ he->stat.weight1 += weight1;
+
+ if (!symbol_conf.cumulate_callchain)
+ return true;
+
+ if (!he->stat_acc) {
+ he->stat_acc = calloc(1, sizeof(struct he_stat));
+ if (!he->stat_acc)
+ return false;
+ }
+
+ 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);
+}
+
+/*
+ * Free all child entries under @he, recursively (hroot_out sub-tree).
+ *
+ * Children are built by c2c_child_entry__alloc(), which BORROWS thread and
+ * ms (plain copy, no thread__get()/map__get()) and OWNS only mem_info (a
+ * clone), stat_acc and the c2c-specific fields (hists, cpuset, nodeset,
+ * nodestr, node_stats). We therefore must NOT call hist_entry__delete()
+ * here: it would thread__zput()/map_symbol__exit() the borrowed refs and
+ * underflow their refcounts. Free exactly the owned resources instead.
+ */
+/*
+ * Free one child entry @child_he and everything below it. Frees exactly the
+ * resources c2c_child_entry__alloc() owns (mem_info clone, stat_acc, hists and
+ * the c2c-specific arrays); the borrowed thread/ms are left untouched.
+ */
+static void c2c_child_he__free(struct hist_entry *child_he)
+{
+ struct c2c_hist_entry *child_c2c = container_of(child_he, struct c2c_hist_entry, he);
+
+ if (child_he->stat_acc)
+ zfree(&child_he->stat_acc);
+
+ if (child_he->mem_info)
+ mem_info__put(child_he->mem_info);
+
+ if (child_c2c->hists) {
+ perf_hpp__reset_output_field(&child_c2c->hists->list);
+ hists__delete_all_entries(&child_c2c->hists->hists);
+ zfree(&child_c2c->hists);
+ }
+
+ zfree(&child_c2c->cpuset);
+ zfree(&child_c2c->nodeset);
+ zfree(&child_c2c->nodestr);
+ zfree(&child_c2c->node_stats);
+
+ c2c_he__free_hierarchy(child_he);
+
+ free(child_c2c);
+}
+
+static void c2c_he__free_hierarchy(struct hist_entry *he)
+{
+ struct rb_node *nd;
+ struct hist_entry *child_he;
+
+ /*
+ * Leaf entries alias hroot_out with sorted_chain (callchains) in a
+ * union, so they have no child hierarchy to free here.
+ */
+ 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);
+ c2c_child_he__free(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 (child_stores == 0) 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__child_stores(l2_he) > 0)
+ surviving++;
+ else {
+ rb_erase_cached(&l2_he->rb_node, &l1_he->hroot_out);
+ c2c_child_he__free(l2_he);
+ }
+ nd = next;
+ }
+
+ if (!surviving) {
+ l1_he->hroot_out = RB_ROOT_CACHED;
+ l1_he->has_children = false;
+ l1_he->unfolded = false;
+ }
+ return surviving;
+}
+
int perf_c2c__browse_function_view(struct hists *hists __maybe_unused)
{
ui__warning("C2C function view is not implemented yet.\n");
--
2.52.0
Add the entry-creation layer: the borrowed-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 cacheline address
(level 3).
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/ui/browsers/c2c-function.c | 277 +++++++++++++++++++++++++-
1 file changed, 271 insertions(+), 6 deletions(-)
diff --git a/tools/perf/ui/browsers/c2c-function.c b/tools/perf/ui/browsers/c2c-function.c
index 1bddc6bdc073..9a7b1353e11c 100644
--- a/tools/perf/ui/browsers/c2c-function.c
+++ b/tools/perf/ui/browsers/c2c-function.c
@@ -29,6 +29,7 @@
#include "../../util/addr_location.h"
#include "../../util/cacheline.h"
#include "../../util/debug.h"
+#include "../../util/dso.h"
#include "../../util/hist.h"
#include "../../util/map.h"
#include "../../util/mem-events.h"
@@ -57,12 +58,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)
@@ -737,7 +760,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;
@@ -867,6 +890,248 @@ static int __maybe_unused c2c_he__prune_empty_writers(struct hist_entry *l1_he)
return surviving;
}
+/* Entry operations for function view */
+static struct hist_entry_ops c2c_function_entry_ops = {
+ .new = c2c_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_he_zalloc(0);
+ if (!child_he)
+ return NULL;
+
+ child_c2c = container_of(child_he, struct c2c_hist_entry, he);
+ memcpy(&child_he->ms, &src_he->ms, sizeof(struct map_symbol));
+
+ 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 = 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:
+ if (child_he->mem_info)
+ mem_info__put(child_he->mem_info);
+ zfree(&child_c2c->cpuset);
+ zfree(&child_c2c->nodeset);
+ zfree(&child_c2c->node_stats);
+ free(child_c2c);
+ 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 ? map__unmap_ip(detail_he->ms.map, sym->start) : detail_he->ip;
+
+ mi = mem_info__new();
+ if (mi) {
+ 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);
+ if (mi)
+ mem_info__put(mi);
+
+ if (he)
+ he->hpp_list = &c2c_ext.function_hists.list;
+
+ return he;
+}
+
+/*
+ * Level 2: another function that writes a cacheline the level-1 function
+ * reads, keyed by DSO and symbol so all code addresses and cachelines for the
+ * same writer function aggregate into one row without merging identically
+ * named functions from different DSOs.
+ */
+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 ? 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: a specific cacheline where the L1/L2 functions contend (keyed by cl_addr). */
+static __maybe_unused struct c2c_hist_entry *
+c2c_function_hists__level3_entry(struct c2c_hist_entry *level2_c2c, u64 cl_addr,
+ 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 hist_entry *iter = rb_entry(*p, struct hist_entry, rb_node);
+ u64 iter_addr = 0;
+
+ if (iter->mem_info) {
+ u64 daddr = mem_info__daddr(iter->mem_info)->addr;
+
+ iter_addr = cl_address(daddr, chk_double_cl);
+ }
+
+ parent = *p;
+ if (cl_addr < iter_addr) {
+ p = &parent->rb_left;
+ } else if (cl_addr > iter_addr) {
+ p = &parent->rb_right;
+ leftmost = false;
+ } else {
+ return container_of(iter, struct c2c_hist_entry, he);
+ }
+ }
+
+ 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;
+
+ c2c_child_entry__insert(level2_he, &level3_c2c->he, p, parent, leftmost);
+
+ return level3_c2c;
+}
+
int perf_c2c__browse_function_view(struct hists *hists __maybe_unused)
{
ui__warning("C2C function view is not implemented yet.\n");
--
2.52.0
Add the builder that walks the cacheline histograms once and, for each
read-side function, records the functions that write the lines it reads
(level 2) and the specific cachelines they contend over (level 3),
aggregating the write traffic per contending function. Add the store-count
resort, the pruning of writers/functions with no contention, and the
finalize pass that computes the per-function cycle totals.
build_function_view_hierarchy() is wired into the browser entry point by
the next patch and is marked __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/ui/browsers/c2c-function.c | 471 +++++++++++++++++++++++++-
1 file changed, 459 insertions(+), 12 deletions(-)
diff --git a/tools/perf/ui/browsers/c2c-function.c b/tools/perf/ui/browsers/c2c-function.c
index 9a7b1353e11c..bb7f967ab904 100644
--- a/tools/perf/ui/browsers/c2c-function.c
+++ b/tools/perf/ui/browsers/c2c-function.c
@@ -47,13 +47,13 @@ struct perf_c2c_ext {
u64 total_cycles;
};
-static struct perf_c2c_ext c2c_ext __maybe_unused;
+static struct perf_c2c_ext c2c_ext;
struct c2c_function_browser {
struct hist_browser hb;
};
-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;
}
@@ -88,7 +88,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;
@@ -224,7 +224,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;
@@ -654,7 +654,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,
@@ -669,7 +669,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,
@@ -719,7 +719,7 @@ 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,
+static void c2c_add_cstats(struct compute_stats *dest,
const struct compute_stats *src)
{
c2c_stats_merge(&dest->rmt_hitm, &src->rmt_hitm);
@@ -729,7 +729,7 @@ 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,
+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;
@@ -860,7 +860,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;
@@ -969,7 +969,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)
@@ -1044,7 +1044,7 @@ c2c_function_hists__level1_entry(struct symbol *sym,
* same writer function aggregate into one row without merging identically
* named functions from different DSOs.
*/
-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)
{
@@ -1091,7 +1091,7 @@ c2c_function_hists__level2_entry(struct c2c_hist_entry *level1_c2c,
}
/* Level 3: a specific cacheline where the L1/L2 functions contend (keyed by cl_addr). */
-static __maybe_unused struct c2c_hist_entry *
+static struct c2c_hist_entry *
c2c_function_hists__level3_entry(struct c2c_hist_entry *level2_c2c, u64 cl_addr,
struct c2c_hist_entry *cacheline_src_he)
{
@@ -1132,6 +1132,453 @@ c2c_function_hists__level3_entry(struct c2c_hist_entry *level2_c2c, u64 cl_addr,
return level3_c2c;
}
+/*
+ * 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, then name. */
+ 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 = arch__compare_symbol_names(he->ms.sym->name,
+ iter->ms.sym->name);
+ else
+ cmp = (iter->ms.sym ? 1 : 0) - (he->ms.sym ? 1 : 0);
+ }
+
+ 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 other 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, u64 cl_addr,
+ 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
+ * -d 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);
+
+ /* Skip the level-1 function itself. */
+ if (!c2c_function_cmp(&he_a->ms, &he_b->ms))
+ continue;
+
+ /*
+ * The level-1 function pays a read-side HITM/peer penalty
+ * (its Cycles %) because someone else writes this cacheline.
+ * 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, cl_addr,
+ cacheline_he);
+ if (!level3_c2c || !c2c_he__add_sharing(level3_c2c, c2c_b)) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /*
+ * Cache the running sum of writer store counts on the
+ * level-2 writer and level-1 function parents, so
+ * hist_entry__child_stores() stays O(1).
+ */
+ level2_c2c->child_stores += c2c_b->stats.store;
+ level1_c2c->child_stores += c2c_b->stats.store;
+ }
+ }
+
+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. report_hierarchy is never set and its hroot_out is already empty, so
+ * hist_entry__delete() will not recurse into borrowed children.
+ */
+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;
+ }
+
+ 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.
+ */
+static void c2c_function_hists__reset(void)
+{
+ 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;
+}
+
+/*
+ * 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
+ */
+static int __maybe_unused build_function_view_hierarchy(void)
+{
+ static const char output_fields[] =
+ "cycles_percent,total_stores,symbol_view";
+ static bool hists_initialized;
+ struct rb_node *nd_cl;
+ int ret;
+
+ c2c_function_hists__reset();
+
+ /*
+ * __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)
+ return ret;
+
+ nd_cl = rb_first_cached(&c2c.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;
+ u64 cl_addr;
+
+ /*
+ * Include any cacheline with sharing activity (HITM, peer,
+ * stores or loads), not just HITM, so totals/sorting reflect
+ * all aggregated traffic surfaced by the function view.
+ */
+ if ((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;
+
+ cl_addr = cl_address(mem_info__daddr(he_cl->mem_info)->addr, chk_double_cl);
+ ret = c2c_function__process_cl(cacheline_he, cl_addr, 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();
+
+ 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);
+ c2c_function_hists__reset();
+ return ret;
+}
+
int perf_c2c__browse_function_view(struct hists *hists __maybe_unused)
{
ui__warning("C2C function view is not implemented yet.\n");
--
2.52.0
Add the browser front end: create/run/delete the hist_browser, the title,
and the 'd' shortcut that opens the existing per-cacheline detail view for
the selected entry. Wire the builder into perf_c2c__browse_function_view()
and refuse to run under report_hierarchy, whose hist_entry__delete()
recursion would put the children's borrowed references.
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/ui/browsers/c2c-function.c | 211 +++++++++++++++++++++++++-
1 file changed, 207 insertions(+), 4 deletions(-)
diff --git a/tools/perf/ui/browsers/c2c-function.c b/tools/perf/ui/browsers/c2c-function.c
index bb7f967ab904..46b3ff854833 100644
--- a/tools/perf/ui/browsers/c2c-function.c
+++ b/tools/perf/ui/browsers/c2c-function.c
@@ -40,7 +40,6 @@
#include "../../c2c.h"
#include "hists.h"
-
struct perf_c2c_ext {
struct c2c_hists function_hists;
/* Cached across all level-1 entries; 0 means "not yet computed". */
@@ -95,6 +94,10 @@ static inline u64 hist_entry__iaddr(struct hist_entry *he)
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 */
@@ -694,6 +697,7 @@ 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;
@@ -1427,6 +1431,37 @@ static void c2c_function__update_symbol_width(struct hist_entry *he)
hists__set_col_len(hists, HISTC_SYMBOL, need);
}
+/*
+ * 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;
+}
+
/*
* Prune writers with no stores, drop functions left with no contending
* writer, sort the survivors by store count, then compute the global total.
@@ -1499,7 +1534,7 @@ static void c2c_function_hists__reset(void)
* L2: writing functions contending with each level-1 function
* L3: shared cachelines for each function pair
*/
-static int __maybe_unused build_function_view_hierarchy(void)
+static int build_function_view_hierarchy(void)
{
static const char output_fields[] =
"cycles_percent,total_stores,symbol_view";
@@ -1579,8 +1614,176 @@ static int __maybe_unused build_function_view_hierarchy(void)
return ret;
}
-int perf_c2c__browse_function_view(struct hists *hists __maybe_unused)
+static int c2c_function_browser__title(struct hist_browser *browser,
+ char *bf, size_t size)
{
- ui__warning("C2C function view is not implemented yet.\n");
+ 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)
+{
+ struct c2c_function_browser *browser;
+
+ if (!hists)
+ return NULL;
+
+ browser = zalloc(sizeof(*browser));
+ if (!browser)
+ return NULL;
+
+ hist_browser__init(&browser->hb, hists);
+
+ 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;
+
+ /*
+ * Note: symbol_conf.report_hierarchy is deliberately left unset.
+ * The generic browser still descends into hroot_out children via
+ * rb_hierarchy_next()/can_goto_child(), which key off he->unfolded,
+ * so 'e'/'+' expands L1 -> L2 -> L3 correctly. Setting the flag would
+ * additionally make hist_entry__delete() recurse hroot_out and free
+ * each child, but our children borrow thread/ms (see
+ * c2c_child_entry__alloc()), so that would underflow their refcounts.
+ * Teardown is handled by c2c_he__free_hierarchy() instead.
+ */
+ return browser;
+}
+
+/*
+ * c2c_function_browser__delete - Free function browser
+ */
+static void c2c_function_browser__delete(struct c2c_function_browser *browser)
+{
+ free(browser);
+}
+
+static int c2c_function_browser__browse_cacheline_detail(struct hist_entry *he_selection,
+ struct hists *hists)
+{
+ struct rb_node *nd;
+ u64 cl_addr;
+
+ if (!he_selection || !he_selection->parent_he ||
+ !he_selection->parent_he->parent_he || !he_selection->mem_info)
+ return -1;
+
+ cl_addr = cl_address(mem_info__daddr(he_selection->mem_info)->addr, chk_double_cl);
+
+ for (nd = rb_first_cached(&hists->entries); nd; nd = rb_next(nd)) {
+ struct hist_entry *he_cl = rb_entry(nd, struct hist_entry, rb_node);
+ u64 this_cl;
+
+ if (!he_cl->mem_info)
+ continue;
+
+ this_cl = cl_address(mem_info__daddr(he_cl->mem_info)->addr, chk_double_cl);
+ if (this_cl == cl_addr)
+ return perf_c2c__browse_cacheline(he_cl);
+ }
+
+ return -1;
+}
+
+/*
+ * perf_c2c__browse_function_view - Browse function view with TAB key support
+ * @hists: Main cacheline histograms
+ *
+ * Returns: 0 on success, negative error code on failure
+ */
+int perf_c2c__browse_function_view(struct hists *hists)
+{
+ struct c2c_function_browser *sym_browser;
+ bool saved_use_callchain = symbol_conf.use_callchain;
+ int key, ret;
+ static const char help[] =
+ " d Display cacheline details for the selected entry\n"
+ " e/+ Expand/collapse the selected entry\n"
+ " TAB/ESC/q Return to the cacheline view\n";
+
+ if (!hists)
+ return -EINVAL;
+
+ /*
+ * The level-2/3 children borrow thread/ms (see c2c_child_entry__alloc())
+ * and are torn down by c2c_he__free_hierarchy(). report_hierarchy would
+ * make hist_entry__delete() recurse into hroot_out and put those borrowed
+ * refs (and call a NULL ops->free), so refuse to run if it is ever set.
+ */
+ if (WARN_ON_ONCE(symbol_conf.report_hierarchy))
+ return -EINVAL;
+
+ /* Disable callchain before building so no callchain structs are allocated. */
+ symbol_conf.use_callchain = false;
+
+ ret = build_function_view_hierarchy();
+ if (ret) {
+ ui__error("Failed to build function view hierarchy (ret=%d)\n", ret);
+ goto out;
+ }
+
+
+ sym_browser = c2c_function_browser__new(&c2c_ext.function_hists.hists);
+ if (!sym_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) {
+ /*
+ * hist_browser__run() resets b.nr_entries from
+ * nr_non_filtered_entries on entry, so refresh the visible-row
+ * count (which includes expanded L2/L3 children) here, before
+ * each run, or the cursor cannot move onto the expanded rows
+ * after returning from the 'd'/'?' windows.
+ */
+ sym_browser->hb.nr_non_filtered_entries =
+ c2c_function__nr_visible_rows(
+ &c2c_ext.function_hists.hists.entries,
+ sym_browser->hb.min_pcnt);
+
+ key = hist_browser__run(&sym_browser->hb, "? - help", true, 0);
+
+ switch (key) {
+ case 'q':
+ case K_TAB:
+ case K_ESC:
+ goto browser_done;
+ case 'd':
+ /* Cacheline detail honors the user's callchain setting. */
+ symbol_conf.use_callchain = saved_use_callchain;
+ c2c_function_browser__browse_cacheline_detail(sym_browser->hb.he_selection,
+ hists);
+ /* 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(&sym_browser->hb.b, help);
+ break;
+ default:
+ break;
+ }
+ }
+
+browser_done:
+ c2c_function_browser__delete(sym_browser);
+out_reset:
+ c2c_function_hists__reset();
+out:
+ symbol_conf.use_callchain = saved_use_callchain;
+ return ret;
+}
--
2.52.0
Describe the function view hierarchy (read-side function -> contending
writer function -> shared cachelines), the per-level indentation, and the
keys, with a worked example.
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 | 46 +++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/tools/perf/Documentation/perf-c2c.txt b/tools/perf/Documentation/perf-c2c.txt
index e57a122b8719..ff05eb20a800 100644
--- a/tools/perf/Documentation/perf-c2c.txt
+++ b/tools/perf/Documentation/perf-c2c.txt
@@ -365,6 +365,52 @@ 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 same data,
+organized around functions rather than cachelines. Every level shows
+functions and cachelines only -- code addresses are not shown here; they
+remain available in the per-cacheline detail view ('d').
+
+ Level 1: the read-side function, sorted by Cycles % (estimated load
+ cycles: HITM, peer-snoop and other-load cycles -- on systems
+ whose default display mode is peer, such as Arm64, the
+ peer-snoop component dominates)
+ Level 2: the functions that write the shared lines, causing the
+ level-1 function's HITMs, sorted by store count
+ Level 3: the specific cachelines where the two functions contend
+
+Each function aggregates all of its code addresses into a single entry,
+and a level-2 writer aggregates all of the cachelines it contends over,
+so a contending 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
+cacheline, 'e'/'+' expands or collapses the current entry, and 'TAB',
+'ESC' or 'q' returns to the cacheline view.
+
+For example, expanding a level-1 function reveals the functions that write
+the lines it reads, and expanding one of those reveals the specific shared
+cachelines:
+
+ Shared Data Functions Table (18 entries, sorted on Cycles %)
+ Cycles Store
+ % count Function / Contending function / Cacheline
+ ----------------------------------------------------------------------
+ - 16.54% 419 - [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 the read-side HITM penalty (16.54% of the
+estimated load cycles). It is contended by pull_rt_task and
+enqueue_pushable_task, which write the shared lines; enqueue_pushable_task
+contends over two cachelines but stays a single row with its total (139)
+shown, and the individual cachelines are underneath. This is the
+false-sharing chain that the cacheline view otherwise leaves to be
+reconstructed by hand.
+
For details please refer to the help window by pressing '?' key.
CREDITS
--
2.52.0
© 2016 - 2026 Red Hat, Inc.