From 89cfd46b7e6cab1d9fee534d4aca946c12065895 Mon Sep 17 00:00:00 2001 From: Jiebin Sun Date: Fri, 21 Aug 2026 13:56:10 +0800 Subject: [PATCH] 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 Reviewed-by: Wangyang Guo Signed-off-by: Jiebin Sun Acked-by: Namhyung Kim Tested-by: Arnaldo Carvalho de Melo Cc: Dapeng Mi Cc: Ian Rogers Cc: James Clark Cc: Thomas Falcon Signed-off-by: Arnaldo Carvalho de Melo --- tools/perf/Documentation/perf-c2c.txt | 12 +++- tools/perf/builtin-c2c.c | 85 ++++++++++++++++++++++++--- tools/perf/util/c2c-function.c | 9 +-- tools/perf/util/c2c.h | 1 + 4 files changed, 90 insertions(+), 17 deletions(-) diff --git a/tools/perf/Documentation/perf-c2c.txt b/tools/perf/Documentation/perf-c2c.txt index 9e58a51c55de..7a0cf31be7ed 100644 --- a/tools/perf/Documentation/perf-c2c.txt +++ b/tools/perf/Documentation/perf-c2c.txt @@ -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) diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c index ee294f12a542..4222ac784fab 100644 --- a/tools/perf/builtin-c2c.c +++ b/tools/perf/builtin-c2c.c @@ -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); diff --git a/tools/perf/util/c2c-function.c b/tools/perf/util/c2c-function.c index 5b6a06a5a067..d410196d0429 100644 --- a/tools/perf/util/c2c-function.c +++ b/tools/perf/util/c2c-function.c @@ -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; diff --git a/tools/perf/util/c2c.h b/tools/perf/util/c2c.h index f04e78e1a2e3..65117108ddb1 100644 --- a/tools/perf/util/c2c.h +++ b/tools/perf/util/c2c.h @@ -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);