diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-22 08:47:50 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-22 08:47:50 -0700 |
| commit | 473f6c8f437b049f8ec015d57cd59bb983b1d85c (patch) | |
| tree | b06f7045dbd0fabfe6b26c523a1ffe30d5697a8c /tools/perf/builtin-c2c.c | |
| parent | 47f05f71ad988c3180bdba807151e6e86ed75aa3 (diff) | |
| parent | 9453bc6a69ef43755f1c28d5688cacdd69fa16bd (diff) | |
Merge tag 'perf-tools-for-v7.3-2026-08-21' of git://git.kernel.org/pub/scm/linux/kernel/git/perf/perf-tools
Pull perf tools updates from Namhyung Kim:
"perf c2c:
- Add 'function view' in perf c2c report TUI (switched by pressing
'TAB' in the cacheline view) to organize samples around functions
rather than cachelines in 3-level hierarchy:
Level 1: Read-side function (sorted by estimated Cycles %)
Level 2: Contending writer functions (sorted by Store count)
Level 3: Shared cacheline addresses
Users can navigate the entries and fold/unfold using 'e' key. An
example output would look like below:
Shared Data Functions Table (19 entries, sorted on Cycles %)
Cycles Store
% count Function / Contending function / Cacheline
----------------------------------------------------------------------
+ 35.67% 876 + [k] cpupri_set
+ 24.31% 424 + [k] pull_rt_task
- 16.53% 555 - [k] dequeue_pushable_task
145 - [k] pull_rt_task
145 0xff2d0082809da080
139 - [k] enqueue_pushable_task
70 0xff2d00a2071f9640
69 0xff2d0082809da000
python module support:
- Extend "perf" python module so that it can be fully functional. The
goal is to run scripts directly, not by 'perf script' command. This
would give better performance as well as more control to build
standalone programs with UI.
- Add LiveSession helper (perf_live.py) to enable live event
collection directly from Python using perf.evlist and
perf.parse_events.
perf stat:
- Add --hide-zero-events option to suppress zero-count events
- Reject conflicting --field-separator and --json-output options
- Fix duplicate event output with --for-each-cgroup
perf sched latency:
- Add -H/--histogram and --hist-mode (log|linear) options to show
scheduler wait latency histograms
- Add --time option to filter analysis by time span in 'perf sched
latency'
ARM CoreSight:
- Synthesize callchains for instruction samples from CoreSight trace
using thread stack ('--itrace=g...')
- Support call indentation ('perf script -F +callindent') to display
call depth hierarchy on branch samples
- Decode ETE (Embedded Trace Extension) exception packets
Build system:
- Add 'make install-build-deps' target to install required packages
- Parallelize JSON and metric pre-computation in jevents.py for
faster builds
Vendor event/metric updates:
- Add Intel Nova Lake events and update tables for existing models
- Update AMD Zen 5 and Zen 6 core events
- Update Arm64 Tegra410 metrics and PowerPC hcalls
Internal changes and fixes:
- Harden trace-event and synthetic event parsing against corrupted
data
- Fix unwinding of multi-threaded processes in libdw unwinder
- Fix memory leaks in various commands and python bindings
- Speed up 'perf test' shell tests"
* tag 'perf-tools-for-v7.3-2026-08-21' of git://git.kernel.org/pub/scm/linux/kernel/git/perf/perf-tools: (232 commits)
perf vendor events arm64: Fix Tegra410 Olympus event 0x0197
perf vendor events arm64: fix swapped MetricGroup for Tegra410 L1 prefetcher metrics
perf evlist: Warn when 'sleep' workload is used without system-wide (-a) option
perf c2c: document function view in perf-c2c man page
perf c2c: add function view browser UI and cacheline detail
perf c2c: build and finalize the function view hierarchy
perf c2c: add function view hierarchy entry creation
perf c2c: add function view stats merge and memory management
perf c2c: add HPP list parsing for function view columns
perf c2c: add column rendering for function view
perf c2c: add function view model skeleton
perf c2c: extract shared data structures into util/c2c.h
perf test sample-parsing: Validate PERF_FORMAT_GROUP values without LOST
perf dso: Replace assert with runtime check in dso__read_symbol()
perf dso: Guard against cache underflow on short reads in dso_cache__memcpy()
perf dso: Use stored fd error instead of stale errno in file_read() and file_size()
perf dso: Guard close() against invalid fd in dso__decompress_kmodule_path()
perf dso: Guard against errno==0 when dso__get_filename() returns NULL
perf build: install-build-deps: add RHEL family devel package mapping
perf build: Remove leftover feature tests for removed cxx and clang support
...
Diffstat (limited to 'tools/perf/builtin-c2c.c')
| -rw-r--r-- | tools/perf/builtin-c2c.c | 221 |
1 files changed, 91 insertions, 130 deletions
diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c index c9584dbedf77..715b75d42f2a 100644 --- a/tools/perf/builtin-c2c.c +++ b/tools/perf/builtin-c2c.c @@ -12,11 +12,14 @@ */ #include <errno.h> #include <inttypes.h> +#include <stdlib.h> +#include <string.h> #include <asm/bug.h> #include <linux/compiler.h> #include <linux/err.h> #include <linux/kernel.h> +#include <linux/string.h> #include <linux/stringify.h> #include <linux/zalloc.h> #include <sys/param.h> @@ -49,46 +52,10 @@ #include "ui/progress.h" #include "ui/ui.h" #include "util/annotate.h" +#include "util/c2c.h" #include "util/symbol.h" #include "util/util.h" -struct c2c_hists { - struct hists hists; - struct perf_hpp_list list; - struct c2c_stats stats; -}; - -struct compute_stats { - struct stats lcl_hitm; - struct stats rmt_hitm; - struct stats lcl_peer; - struct stats rmt_peer; - struct stats load; -}; - -struct c2c_hist_entry { - struct c2c_hists *hists; - struct evsel *evsel; - struct c2c_stats stats; - unsigned long *cpuset; - unsigned long *nodeset; - struct c2c_stats *node_stats; - unsigned int cacheline_idx; - - struct compute_stats cstats; - - unsigned long paddr; - unsigned long paddr_cnt; - bool paddr_zero; - char *nodestr; - - /* - * must be at the end, - * because of its callchain dynamic entry - */ - struct hist_entry he; -}; - static char const *coalesce_default = "iaddr"; struct perf_c2c { @@ -226,7 +193,6 @@ he__get_c2c_hists(struct hist_entry *he, ret = c2c_hists__init(hists, sort, nr_header_lines, env); if (ret) { - perf_hpp__reset_output_field(&hists->list); c2c_he->hists = NULL; free(hists); return NULL; @@ -458,36 +424,6 @@ static const char * const __usage_report[] = { static const char * const *report_c2c_usage = __usage_report; -#define C2C_HEADER_MAX 2 - -struct c2c_header { - struct { - const char *text; - int span; - } line[C2C_HEADER_MAX]; -}; - -struct c2c_dimension { - struct c2c_header header; - const char *name; - int width; - struct sort_entry *se; - - int64_t (*cmp)(struct perf_hpp_fmt *fmt, - struct hist_entry *, struct hist_entry *); - int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, - struct hist_entry *he); - int (*color)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, - struct hist_entry *he); -}; - -struct c2c_fmt { - struct perf_hpp_fmt fmt; - struct c2c_dimension *dim; -}; - -#define SYMBOL_WIDTH 30 - static struct c2c_dimension dim_symbol; static struct c2c_dimension dim_srcline; @@ -1389,23 +1325,6 @@ cl_idx_empty_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, return scnprintf(hpp->buf, hpp->size, "%*s", width, ""); } -#define HEADER_LOW(__h) \ - { \ - .line[1] = { \ - .text = __h, \ - }, \ - } - -#define HEADER_BOTH(__h0, __h1) \ - { \ - .line[0] = { \ - .text = __h0, \ - }, \ - .line[1] = { \ - .text = __h1, \ - }, \ - } - #define HEADER_SPAN(__h0, __h1, __s) \ { \ .line[0] = { \ @@ -1928,22 +1847,6 @@ static struct c2c_dimension *dimensions[] = { NULL, }; -static void fmt_free(struct perf_hpp_fmt *fmt) -{ - struct c2c_fmt *c2c_fmt; - - c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); - free(c2c_fmt); -} - -static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) -{ - struct c2c_fmt *c2c_a = container_of(a, struct c2c_fmt, fmt); - struct c2c_fmt *c2c_b = container_of(b, struct c2c_fmt, fmt); - - return c2c_a->dim == c2c_b->dim; -} - static struct c2c_dimension *get_dimension(const char *name) { unsigned int i; @@ -2021,8 +1924,8 @@ static struct c2c_fmt *get_format(const char *name) fmt->header = c2c_header; fmt->width = c2c_width; fmt->collapse = dim->se ? c2c_se_collapse : dim->cmp; - fmt->equal = fmt_equal; - fmt->free = fmt_free; + fmt->equal = c2c_fmt_equal; + fmt->free = c2c_fmt_free; return c2c_fmt; } @@ -2063,26 +1966,38 @@ static int c2c_hists__init_sort(struct perf_hpp_list *hpp_list, char *name, stru return 0; } -#define PARSE_LIST(_list, _fn) \ - do { \ - char *tmp, *tok; \ - ret = 0; \ - \ - if (!_list) \ - break; \ - \ - for (tok = strtok_r((char *)_list, ", ", &tmp); \ - tok; tok = strtok_r(NULL, ", ", &tmp)) { \ - ret = _fn(hpp_list, tok, env); \ - if (ret == -EINVAL) { \ - pr_err("Invalid --fields key: `%s'", tok); \ - break; \ - } else if (ret == -ESRCH) { \ - pr_err("Unknown --fields key: `%s'", tok); \ - break; \ - } \ - } \ - } while (0) +static int __hpp_list__parse(struct perf_hpp_list *hpp_list, char *_list, struct perf_env *env, + int (*_fn)(struct perf_hpp_list *hpp_list, char *name, struct perf_env *env)) +{ + char *tmp, *tok; + int ret = 0; + + if (!_list) + return 0; + + for (tok = strtok_r(_list, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) { + ret = _fn(hpp_list, tok, env); + switch (ret) { + case 0: + continue; + case -EINVAL: + pr_err("Invalid --fields key: `%s'", tok); + goto out; + case -ESRCH: + pr_err("Unknown --fields key: `%s'", tok); + goto out; + default: { + char buf[STRERR_BUFSIZE]; + + pr_err("%s for --fields key: `%s'", + str_error_r(-ret, buf, sizeof(buf)), tok); + goto out; + } + } + } +out: + return ret; +} static int hpp_list__parse(struct perf_hpp_list *hpp_list, const char *output_, @@ -2093,8 +2008,18 @@ static int hpp_list__parse(struct perf_hpp_list *hpp_list, char *sort = sort_ ? strdup(sort_) : NULL; int ret; - PARSE_LIST(output, c2c_hists__init_output); - PARSE_LIST(sort, c2c_hists__init_sort); + /* strdup() returns NULL on OOM, don't silently treat as empty */ + if ((output_ && !output) || (sort_ && !sort)) { + ret = -ENOMEM; + goto out; + } + + ret = __hpp_list__parse(hpp_list, output, env, c2c_hists__init_output); + if (ret) + goto out; + ret = __hpp_list__parse(hpp_list, sort, env, c2c_hists__init_sort); + if (ret) + goto out; /* copy sort keys to output fields */ perf_hpp__setup_output_field(hpp_list); @@ -2111,6 +2036,7 @@ static int hpp_list__parse(struct perf_hpp_list *hpp_list, perf_hpp__append_sort_keys(&hists->list); #endif +out: free(output); free(sort); return ret; @@ -2121,6 +2047,8 @@ static int c2c_hists__init(struct c2c_hists *hists, int nr_header_lines, struct perf_env *env) { + int ret; + __hists__init(&hists->hists, &hists->list); /* @@ -2133,7 +2061,13 @@ static int c2c_hists__init(struct c2c_hists *hists, /* Overload number of header lines.*/ hists->list.nr_header_lines = nr_header_lines; - return hpp_list__parse(&hists->list, /*output=*/NULL, sort, env); + ret = hpp_list__parse(&hists->list, /*output=*/NULL, sort, env); + + /* Unregister any formats added before the failure point */ + if (ret) + perf_hpp__reset_output_field(&hists->list); + + return ret; } static int c2c_hists__reinit(struct c2c_hists *c2c_hists, @@ -2141,8 +2075,16 @@ static int c2c_hists__reinit(struct c2c_hists *c2c_hists, const char *sort, struct perf_env *env) { + int ret; + perf_hpp__reset_output_field(&c2c_hists->list); - return hpp_list__parse(&c2c_hists->list, output, sort, env); + ret = hpp_list__parse(&c2c_hists->list, output, sort, env); + + /* Unregister any formats added before the failure point */ + if (ret) + perf_hpp__reset_output_field(&c2c_hists->list); + + return ret; } #define DISPLAY_LINE_LIMIT 0.001 @@ -2281,6 +2223,7 @@ static int resort_cl_cb(struct hist_entry *he, void *arg) struct c2c_hist_entry *c2c_he; struct c2c_hists *c2c_hists; bool display = he__display(he, &c2c.shared_clines_stats); + int ret; c2c_he = container_of(he, struct c2c_hist_entry, he); c2c_hists = c2c_he->hists; @@ -2291,7 +2234,9 @@ static int resort_cl_cb(struct hist_entry *he, void *arg) c2c_he->cacheline_idx = idx++; calc_width(c2c_he); - c2c_hists__reinit(c2c_hists, c2c.cl_output, c2c.cl_resort, env); + ret = c2c_hists__reinit(c2c_hists, c2c.cl_output, c2c.cl_resort, env); + if (ret) + return ret; hists__collapse_resort(&c2c_hists->hists, NULL); hists__output_resort_cb(&c2c_hists->hists, NULL, filter_cb); @@ -2800,11 +2745,18 @@ perf_c2c_browser__new(struct hists *hists) static int perf_c2c__hists_browse(struct hists *hists) { + struct c2c_function_view_args func_args = { + .cl_hists = &c2c.hists, + .cl_sort = c2c.cl_sort, + .symbol_full = c2c.symbol_full, + .browse_cacheline = perf_c2c__browse_cacheline, + }; struct hist_browser *browser; int key = -1; static const char help[] = " d Display cacheline details \n" " ENTER Toggle callchains (if present) \n" + " TAB Switch to function view\n" " q Quit \n"; browser = perf_c2c_browser__new(hists); @@ -2826,6 +2778,9 @@ static int perf_c2c__hists_browse(struct hists *hists) case 'd': perf_c2c__browse_cacheline(browser->he_selection); break; + case '\t': + perf_c2c__browse_function_view(&func_args); + break; case '?': ui_browser__help_window(&browser->b, help); break; @@ -3356,13 +3311,19 @@ static int perf_c2c__report(int argc, const char **argv) else if (c2c.display == DISPLAY_SNP_PEER) sort_str = "tot_peer"; - c2c_hists__reinit(&c2c.hists, output_str, sort_str, perf_session__env(session)); + err = c2c_hists__reinit(&c2c.hists, output_str, sort_str, perf_session__env(session)); + if (err) { + pr_err("Failed to reinitialize hists\n"); + goto out_mem2node; + } ui_progress__init(&prog, c2c.hists.hists.nr_entries, "Sorting..."); hists__collapse_resort(&c2c.hists.hists, NULL); hists__output_resort_cb(&c2c.hists.hists, &prog, resort_shared_cl_cb); - hists__iterate_cb(&c2c.hists.hists, resort_cl_cb, perf_session__env(session)); + err = hists__iterate_cb(&c2c.hists.hists, resort_cl_cb, perf_session__env(session)); + if (err) + goto out_mem2node; ui_progress__finish(); |
