perf c2c: Add stdio support for the function view

The function view is currently TUI-only, so it cannot be used by builds
without SLANG support, when output is piped, or from a script. Add a
--function option that prints the fully expanded three-level hierarchy to
stdout.

Keep the stdio renderer in builtin-c2c.c and reuse the common function-view
model introduced by the merged series. Export only the coalescing-field
capability check from the model, preserving the util/UI boundary and
leaving the TUI object in libperf-ui.a.

Stop padding the final identity column in symbol_view_entry(). The generic
formatter pads non-final columns but deliberately leaves the final column
unpadded, avoiding trailing whitespace in function-view table rows. The TUI
remains unchanged because its browser clears the rest of each rendered row.

--function implies --stdio and is rejected together with --stats. Validate
the iaddr requirement before processing events. Return function-view build
failures from the report command, and preserve TUI browser errors when
converting the display helpers to return a status.

Committer notes:

  root@x2:~# uname -r
  7.2.5-200.fc44.x86_64
  root@x2:~# rpm -q kernel-debuginfo
  kernel-debuginfo-7.2.5-200.fc44.x86_64
  root@x2:~# perf c2c record perf bench futex hash -t 4 -r 1 -s > /dev/null 2>&1
  root@x2:~# perf c2c report --stdio --function | grep "Functions Table" -A25
             Shared Data Functions Table
  =================================================
  #
  #    Cycles    Store
  #         %    count  Function / Contending function / Cacheline
  # .........  .......  ..............................
  #
    -  47.89%      703  - [k] _raw_spin_lock
                   322    - [k] _raw_spin_lock
                    24        0xffff8b6dcbadf040
                    23        0xffff8b6dcbadf2c0
                    23        0xffff8b6dcbadf080
                    23        0xffff8b6dcbadf380
                    22        0xffff8b6dcbadf280
                    22        0xffff8b6dcbadf140
                    22        0xffff8b6dcbadf340
                    21        0xffff8b6dcbadf300
                    21        0xffff8b6dcbadf400
                    20        0xffff8b6dcbadf1c0
                    19        0xffff8b6dcbadf3c0
                    19        0xffff8b6dcbadf180
                    19        0xffff8b6dcbadf0c0
                    17        0xffff8b6dcbadf100
                    16        0xffff8b6dcbadf240
                    11        0xffff8b6dcbadf200
                   205    - [k] futex_q_unlock
  root@x2:~#

Reviewed-by: Tianyou Li <tianyou.li@intel.com>
Reviewed-by: Wangyang Guo <wangyang.guo@intel.com>
Signed-off-by: Jiebin Sun <jiebin.sun@intel.com>
Acked-by: Namhyung Kim <namhyung@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Thomas Falcon <thomas.falcon@intel.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Jiebin Sun 2026-08-21 13:56:10 +08:00 committed by Arnaldo Carvalho de Melo
parent f4a58c7164
commit 89cfd46b7e
4 changed files with 90 additions and 17 deletions

View File

@ -107,6 +107,10 @@ REPORT OPTIONS
--stats::
Display only statistic tables and force stdio mode.
--function::
Display the function view and force stdio mode. This requires `iaddr`
in the cacheline coalescing fields and cannot be used with `--stats`.
--full-symbols::
Display full length of symbols.
@ -360,6 +364,10 @@ Following tables are displayed:
Shared Cache Line Distribution Pareto
- list of all accessed offsets for each cacheline
With `--function`, the cacheline and Pareto tables are replaced by a fully
expanded Shared Data Functions Table. Its three levels are the read-side
function, contending writer, and shared cacheline, as detailed below.
TUI OUTPUT
----------
The TUI output provides interactive interface to navigate
@ -374,8 +382,8 @@ Verbose mode also includes code addresses in function rows, and code addresses
remain available in the per-cacheline detail view ('d').
The function view requires `iaddr` in the cacheline coalescing fields. If
`--coalesce` omits it, TAB reports that the view is unavailable rather than
attributing already-coalesced samples to an arbitrary function.
`--coalesce` omits it, TAB or `--function` reports that the view is unavailable
rather than attributing already-coalesced samples to an arbitrary function.
Level 1: the read-side function, sorted by Cycles % (estimated load
cycles: HITM, peer-snoop and other-load cycles)

View File

@ -76,6 +76,7 @@ struct perf_c2c {
bool show_mem_region;
bool use_stdio;
bool stats_only;
bool function_view;
bool symbol_full;
bool stitch_lbr;
@ -2624,7 +2625,48 @@ static void print_memory_ranges_info(FILE *out, struct perf_session *session)
}
}
static void perf_c2c__hists_fprintf(FILE *out, struct perf_session *session)
static void c2c_function__unfold_all(struct rb_root_cached *root)
{
struct rb_node *nd;
for (nd = rb_first_cached(root); nd; nd = rb_next(nd)) {
struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
if (!he->has_children)
continue;
he->unfolded = true;
c2c_function__unfold_all(&he->hroot_out);
}
}
static int perf_c2c__function_fprintf(FILE *out)
{
bool saved_use_callchain = symbol_conf.use_callchain;
struct hists *hists;
int ret;
/* Function-view entries aggregate samples and never display callchains. */
symbol_conf.use_callchain = false;
ret = c2c_function__build(&c2c.hists, c2c.cl_sort, c2c.symbol_full,
&hists);
if (ret) {
if (ret == -EOPNOTSUPP)
pr_err("The function view requires iaddr in --coalesce.\n");
else
pr_err("Failed to build function view hierarchy (ret=%d)\n", ret);
goto out;
}
/* Match fold signs to hists__fprintf()'s forced child traversal. */
c2c_function__unfold_all(&hists->entries);
hists__fprintf(hists, true, 0, 0, 0, out, true);
c2c_function__reset();
out:
symbol_conf.use_callchain = saved_use_callchain;
return ret;
}
static int perf_c2c__hists_fprintf(FILE *out, struct perf_session *session)
{
setup_pager();
@ -2635,7 +2677,17 @@ static void perf_c2c__hists_fprintf(FILE *out, struct perf_session *session)
print_c2c_info(out, session);
if (c2c.stats_only)
return;
return 0;
if (c2c.function_view) {
fprintf(out, "\n");
fprintf(out, "=================================================\n");
fprintf(out, " Shared Data Functions Table\n");
fprintf(out, "=================================================\n");
fprintf(out, "#\n");
return perf_c2c__function_fprintf(out);
}
print_memory_ranges_info(out, session);
@ -2654,6 +2706,7 @@ static void perf_c2c__hists_fprintf(FILE *out, struct perf_session *session)
fprintf(out, "#\n");
print_pareto(out, perf_session__env(session));
return 0;
}
#ifdef HAVE_SLANG_SUPPORT
@ -2890,18 +2943,18 @@ static int perf_c2c__hists_browse(struct hists *hists)
return 0;
}
static void perf_c2c_display(struct perf_session *session)
static int perf_c2c_display(struct perf_session *session)
{
if (use_browser == 0)
perf_c2c__hists_fprintf(stdout, session);
else
perf_c2c__hists_browse(&c2c.hists.hists);
return perf_c2c__hists_fprintf(stdout, session);
return perf_c2c__hists_browse(&c2c.hists.hists);
}
#else
static void perf_c2c_display(struct perf_session *session)
static int perf_c2c_display(struct perf_session *session)
{
use_browser = 0;
perf_c2c__hists_fprintf(stdout, session);
return perf_c2c__hists_fprintf(stdout, session);
}
#endif /* HAVE_SLANG_SUPPORT */
@ -3180,6 +3233,8 @@ static int perf_c2c__report(int argc, const char **argv)
OPT_BOOLEAN(0, "stdio", &c2c.use_stdio, "Use the stdio interface"),
OPT_BOOLEAN(0, "stats", &c2c.stats_only,
"Display only statistic tables (implies --stdio)"),
OPT_BOOLEAN(0, "function", &c2c.function_view,
"Display the function view (implies --stdio)"),
OPT_BOOLEAN(0, "full-symbols", &c2c.symbol_full,
"Display full length of symbols"),
OPT_BOOLEAN(0, "no-source", &no_source,
@ -3219,6 +3274,11 @@ static int perf_c2c__report(int argc, const char **argv)
PARSE_OPT_STOP_AT_NON_OPTION);
if (argc)
usage_with_options(report_c2c_usage, options);
if (c2c.stats_only && c2c.function_view) {
pr_err("--stats and --function cannot be used together.\n");
err = -EINVAL;
goto out;
}
#ifndef HAVE_SLANG_SUPPORT
c2c.use_stdio = true;
@ -3226,6 +3286,8 @@ static int perf_c2c__report(int argc, const char **argv)
if (c2c.stats_only)
c2c.use_stdio = true;
if (c2c.function_view)
c2c.use_stdio = true;
/**
* Annotation related options disassembler_style, objdump_path are set
@ -3299,6 +3361,11 @@ static int perf_c2c__report(int argc, const char **argv)
pr_debug("Failed to initialize hists\n");
goto out_session;
}
if (c2c.function_view && !c2c_function__has_iaddr(c2c.cl_sort)) {
pr_err("The function view requires iaddr in --coalesce.\n");
err = -EINVAL;
goto out_session;
}
err = c2c_hists__init(&c2c.hists, "dcacheline", 2, perf_session__env(session));
if (err) {
@ -3452,7 +3519,7 @@ static int perf_c2c__report(int argc, const char **argv)
goto out_str;
}
perf_c2c_display(session);
err = perf_c2c_display(session);
out_str:
free(output_str);

View File

@ -305,8 +305,8 @@ symbol_view_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
char symbuf[32];
scnprintf(symbuf, sizeof(symbuf), "0x%" PRIx64, addr);
ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%-*.*s",
text_width, text_width, symbuf);
ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%.*s",
text_width, symbuf);
} else {
/* Level 1 and level 2 are both functions. */
size_t cell_size;
@ -331,9 +331,6 @@ symbol_view_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
len = min_t(size_t, len, cell_size - 1);
ret += len;
if (len < text_width)
ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%*s",
text_width - len, "");
}
return ret;
@ -1519,7 +1516,7 @@ void c2c_function__reset(void)
symbol_conf.use_callchain = saved_use_callchain;
}
static bool c2c_function__has_iaddr(const char *cl_sort)
bool c2c_function__has_iaddr(const char *cl_sort)
{
const char *field = cl_sort;

View File

@ -108,6 +108,7 @@ bool c2c_fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b);
int c2c_function__build(struct c2c_hists *cl_hists, const char *cl_sort,
bool symbol_full, struct hists **hists);
void c2c_function__reset(void);
bool c2c_function__has_iaddr(const char *cl_sort);
/* Valid only between a successful build and c2c_function__reset(). */
struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he);