// SPDX-License-Identifier: GPL-2.0/* * C2C function model - function-level cacheline sharing analysis * * Displays a 3-level hierarchy showing which functions share cachelines: * Level 1: Read-side functions sorted by Cycles % (estimated load cycles) * Level 2: Functions sampled writing the shared lines read by level 1 * Level 3: The specific cachelines where the two functions contend * * Builds the hierarchy from the existing cacheline histograms * (c2c_hist_entry->hists), reusing the shared c2c data structures. */ #include <errno.h>#include <inttypes.h>#include <stdlib.h>#include <string.h>#include <tools/libc_compat.h> /* reallocarray */#include <linux/list.h>#include <linux/rbtree.h>#include <linux/zalloc.h> #include "addr_location.h"#include "c2c.h"#include "cacheline.h"#include "debug.h"#include "dso.h"#include "hist.h"#include "map.h"#include "mem-events.h"#include "mem-info.h"#include "sort.h"#include "symbol.h"#include "thread.h" struct c2c_function_model { struct c2c_hists function_hists; /* Total estimated cycles across all level-1 entries. */ u64 total_cycles; /* Source cacheline histograms; not owned here. */ struct c2c_hists *cl_hists; /* --coalesce field list, used to require iaddr. */ const char *cl_sort; /* Do not cap long symbol names. */ bool symbol_full;}; static struct c2c_function_model c2c_ext; static inline u64 c2c_hitm_count(const struct c2c_stats *stats){ return stats->tot_hitm;} static int64_t c2c_function_cmp(const struct map_symbol *left, const struct map_symbol *right){ 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 u64 hist_entry__iaddr(struct hist_entry *he){ if (he->mem_info) return mem_info__iaddr(he->mem_info)->addr; return he->ip;} /* * Hierarchy levels (by depth): L1 = read-side function, L2 = the writing * function it contends with, L3 = the specific shared cacheline. */static inline bool hist_entry__is_cacheline(struct hist_entry *he){ return he->parent_he && he->parent_he->parent_he; /* level 3: cacheline */} /* Spaces of indent per hierarchy level, like the normal report view. */#define C2C_FUNC_INDENT 2 /* Width of the folded-sign prefix ("%c ") each identity cell emits. */#define C2C_FUNC_FOLD_WIDTH 2 /* * Write he->depth levels of leading indentation into @buf, so lower-level * entries are visually nested under their parent. Returns bytes written. */static int hist_entry__indent(struct hist_entry *he, char *buf, size_t size){ int indent = he->depth * C2C_FUNC_INDENT; if (indent <= 0 || (size_t)indent >= size) return 0; return scnprintf(buf, size, "%*s", indent, "");} static int symbol_width(struct hists *hists, struct sort_entry *se){ int width = hists__col_len(hists, se->se_width_idx); /* * Cap long symbol names as the cacheline view does. The stored column * length is grown up front to fit the deepest, longest identity cell * (including a level-3 cacheline address), so this cap never shrinks the * column below what the cacheline address needs. */ if (!c2c_ext.symbol_full && width > SYMBOL_WIDTH) width = SYMBOL_WIDTH; return width;} static struct c2c_dimension dim_symbol_view; /* * c2c_width - Calculate width for a C2C column in function view */static int c2c_width(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp __maybe_unused, struct hists *hists){ struct c2c_fmt *c2c_fmt; struct c2c_dimension *dim; c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); dim = c2c_fmt->dim; if (dim == &dim_symbol_view) return symbol_width(hists, dim->se); return dim->se ? hists__col_len(hists, dim->se->se_width_idx) : dim->width;} static int c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hists *hists, int line, int *span){ struct c2c_fmt *c2c_fmt; struct c2c_dimension *dim; const char *text = NULL; int width = c2c_width(fmt, hpp, hists); c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); dim = c2c_fmt->dim; if (dim->se) { text = dim->header.line[line].text; /* Use the last line from sort_entry if not defined. */ if (!text && line == hists->hpp_list->nr_header_lines - 1) text = dim->se->se_header; } else { text = dim->header.line[line].text; if (span) { if (*span) { (*span)--; return 0; } *span = dim->header.line[line].span; } } if (!text) text = ""; return scnprintf(hpp->buf, hpp->size, "%*s", width, text);} /* * Return the estimated total cycles for a c2c_hist_entry * (rmt_hitm + lcl_hitm + rmt_peer + lcl_peer + other loads). */static u64 c2c_hist_entry__cycles(struct c2c_hist_entry *c2c_he){ struct compute_stats *cs = &c2c_he->cstats; double cycles = 0; /* * compute_stats() in builtin-c2c.c routes each load sample into exactly * one cstats bucket (rmt_hitm, lcl_hitm, rmt_peer, lcl_peer or plain * load), so each bucket's cycle total is its mean times its own sample * count. Summing the per-bucket totals avoids both dropping peer-snoop * cycles and double counting a sample that carries several data-source * flags (e.g. Arm SPE sets HITM and PEER on the same load), which would * happen if the mean were multiplied by the non-exclusive stats counts. */ cycles += avg_stats(&cs->rmt_hitm) * cs->rmt_hitm.n; cycles += avg_stats(&cs->lcl_hitm) * cs->lcl_hitm.n; cycles += avg_stats(&cs->rmt_peer) * cs->rmt_peer.n; cycles += avg_stats(&cs->lcl_peer) * cs->lcl_peer.n; cycles += avg_stats(&cs->load) * cs->load.n; return (u64)cycles;} /* Sum c2c_hist_entry__cycles() across all level-1 entries. */static u64 c2c_ext__total_cycles(void){ struct rb_node *nd; u64 total = 0; for (nd = rb_first_cached(&c2c_ext.function_hists.hists.entries); nd; nd = rb_next(nd)) { struct c2c_hist_entry *c2c_he = rb_entry(nd, struct c2c_hist_entry, he.rb_node); total += c2c_hist_entry__cycles(c2c_he); } return total;} /* * Store count shown in the column: a level-3 cacheline leaf shows its parent * level-2 writer's stores on that line, not all stores on the line. A level-2 * writer shows the sum across its level-3 cachelines. A level-1 reader shows * the sum across all included writers on the cachelines it reads; this is not * the reader function's own store count and is not additive across readers. */static u64 hist_entry__displayed_stores(struct hist_entry *he){ struct c2c_hist_entry *c2c_he = container_of(he, struct c2c_hist_entry, he); struct rb_node *nd; u64 stores = 0; /* Level-2/3 entries already aggregate the stores they represent. */ if (he->parent_he) return c2c_he->stats.store; for (nd = rb_first_cached(&he->hroot_out); nd; nd = rb_next(nd)) { struct c2c_hist_entry *child_c2c = rb_entry(nd, struct c2c_hist_entry, he.rb_node); stores += child_c2c->stats.store; } return stores;} static inttotal_stores_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hist_entry *he){ int width = c2c_width(fmt, hpp, he->hists); u64 total = hist_entry__displayed_stores(he); return scnprintf(hpp->buf, hpp->size, "%*" PRIu64, width, total);} /* * symbol_view_entry - Render the unified, indented identity column. * * All three levels share this single column so the hierarchy reads top-down * with progressive indentation, like the normal report hierarchy view. It is * a function-centric view with no dedicated code-address column. Verbose * function rows can still include a representative address: * L1 read-side function: "- [k] cpupri_set" * L2 writing function: " - [k] pull_rt_task" * L3 shared cacheline: " 0xff2d0082809da080" */static intsymbol_view_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hist_entry *he){ int width = c2c_width(fmt, hpp, he->hists); int text_width; int ret; char folded_sign; ret = hist_entry__indent(he, hpp->buf, hpp->size); folded_sign = he->has_children ? (he->unfolded ? '-' : '+') : ' '; ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%c ", folded_sign); text_width = width - ret; if (text_width <= 0) return ret; if (hist_entry__is_cacheline(he)) { /* Level 3: the shared cacheline address. */ u64 addr = he->mem_info ? cl_address(mem_info__daddr(he->mem_info)->addr, chk_double_cl) : 0; char symbuf[32]; scnprintf(symbuf, sizeof(symbuf), "0x%" PRIx64, addr); ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%-*.*s", text_width, text_width, symbuf); } else { /* Level 1 and level 2 are both functions. */ size_t cell_size; int len; if ((size_t)ret >= hpp->size) return ret; cell_size = min_t(size_t, hpp->size - ret, (size_t)text_width + 1); len = sort_sym.se_snprintf(he, hpp->buf + ret, cell_size, text_width); /* * se_snprintf() accumulates repsep_snprintf() calls, which cap * their return at the remaining size - 1 rather than reporting * what the format would have needed, so len stays below * cell_size. Clamp anyway so ret cannot leave hpp->buf. */ if (len < 0) len = 0; else len = min_t(size_t, len, cell_size - 1); ret += len; if (len < text_width) ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%*s", text_width - len, ""); } return ret;} /* * cycles_percent_entry - Render cycles percentage column */static intcycles_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 c2c_function__build() once the L1 tree is built. */ total_cycles = c2c_ext.total_cycles; pct = total_cycles > 0 ? (double)fn_cycles / total_cycles * 100.0 : 0.0; /* 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_tcycles_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_ttotal_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[] = { &dim_cycles_percent, &dim_total_stores, &dim_symbol_view, NULL,}; static struct c2c_dimension *get_function_dimension(const char *name){ unsigned int i; for (i = 0; function_view_dimensions[i]; i++) { struct c2c_dimension *dim = function_view_dimensions[i]; if (!strcmp(dim->name, name)) return dim; } return NULL;} /* Wrappers so sort_entry-backed dimensions sort/collapse via their se. */static int64_t c2c_se_cmp(struct perf_hpp_fmt *fmt, struct hist_entry *a, struct hist_entry *b){ struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); struct c2c_dimension *dim = c2c_fmt->dim; return dim->se->se_cmp(a, b);} static int64_t c2c_se_collapse(struct perf_hpp_fmt *fmt, struct hist_entry *a, struct hist_entry *b){ struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); struct c2c_dimension *dim = c2c_fmt->dim; int64_t (*collapse_fn)(struct hist_entry *a, struct hist_entry *b); collapse_fn = dim->se->se_collapse ?: dim->se->se_cmp; return collapse_fn(a, b);} static int64_t c2c_se_sort(struct perf_hpp_fmt *fmt, struct hist_entry *a, struct hist_entry *b){ struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); struct c2c_dimension *dim = c2c_fmt->dim; int64_t (*sort_fn)(struct hist_entry *a, struct hist_entry *b); sort_fn = dim->se->se_sort ?: dim->se->se_cmp; return sort_fn(a, b);} /* * Build the c2c_fmt for @name. Returns: * 0 and *fmtp set on success; * -ENOENT and *fmtp = NULL if @name is not a function-view dimension; * -ENOMEM if allocation failed (distinct from -ENOENT so * the caller does not misreport it as an * "invalid field"). */static int get_function_format(const char *name, struct c2c_fmt **fmtp){Lines 1–500 of 1642. Load more
// SPDX-License-Identifier: GPL-2.0 /* * C2C function model - function-level cacheline sharing analysis * * Displays a 3-level hierarchy showing which functions share cachelines: * Level 1: Read-side functions sorted by Cycles % (estimated load cycles) * Level 2: Functions sampled writing the shared lines read by level 1 * Level 3: The specific cachelines where the two functions contend * * Builds the hierarchy from the existing cacheline histograms * (c2c_hist_entry->hists), reusing the shared c2c data structures. */ #include <errno.h> #include <inttypes.h> #include <stdlib.h> #include <string.h> #include <tools/libc_compat.h> /* reallocarray */ #include <linux/list.h> #include <linux/rbtree.h> #include <linux/zalloc.h> #include "addr_location.h" #include "c2c.h" #include "cacheline.h" #include "debug.h" #include "dso.h" #include "hist.h" #include "map.h" #include "mem-events.h" #include "mem-info.h" #include "sort.h" #include "symbol.h" #include "thread.h" struct c2c_function_model { struct c2c_hists function_hists; /* Total estimated cycles across all level-1 entries. */ u64 total_cycles; /* Source cacheline histograms; not owned here. */ struct c2c_hists *cl_hists; /* --coalesce field list, used to require iaddr. */ const char *cl_sort; /* Do not cap long symbol names. */ bool symbol_full; }; static struct c2c_function_model c2c_ext; static inline u64 c2c_hitm_count(const struct c2c_stats *stats) { return stats->tot_hitm; } static int64_t c2c_function_cmp(const struct map_symbol *left, const struct map_symbol *right) { 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 u64 hist_entry__iaddr(struct hist_entry *he) { if (he->mem_info) return mem_info__iaddr(he->mem_info)->addr; return he->ip; } /* * Hierarchy levels (by depth): L1 = read-side function, L2 = the writing * function it contends with, L3 = the specific shared cacheline. */ static inline bool hist_entry__is_cacheline(struct hist_entry *he) { return he->parent_he && he->parent_he->parent_he; /* level 3: cacheline */ } /* Spaces of indent per hierarchy level, like the normal report view. */ #define C2C_FUNC_INDENT 2 /* Width of the folded-sign prefix ("%c ") each identity cell emits. */ #define C2C_FUNC_FOLD_WIDTH 2 /* * Write he->depth levels of leading indentation into @buf, so lower-level * entries are visually nested under their parent. Returns bytes written. */ static int hist_entry__indent(struct hist_entry *he, char *buf, size_t size) { int indent = he->depth * C2C_FUNC_INDENT; if (indent <= 0 || (size_t)indent >= size) return 0; return scnprintf(buf, size, "%*s", indent, ""); } static int symbol_width(struct hists *hists, struct sort_entry *se) { int width = hists__col_len(hists, se->se_width_idx); /* * Cap long symbol names as the cacheline view does. The stored column * length is grown up front to fit the deepest, longest identity cell * (including a level-3 cacheline address), so this cap never shrinks the * column below what the cacheline address needs. */ if (!c2c_ext.symbol_full && width > SYMBOL_WIDTH) width = SYMBOL_WIDTH; return width; } static struct c2c_dimension dim_symbol_view; /* * c2c_width - Calculate width for a C2C column in function view */ static int c2c_width(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp __maybe_unused, struct hists *hists) { struct c2c_fmt *c2c_fmt; struct c2c_dimension *dim; c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); dim = c2c_fmt->dim; if (dim == &dim_symbol_view) return symbol_width(hists, dim->se); return dim->se ? hists__col_len(hists, dim->se->se_width_idx) : dim->width; } static int c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hists *hists, int line, int *span) { struct c2c_fmt *c2c_fmt; struct c2c_dimension *dim; const char *text = NULL; int width = c2c_width(fmt, hpp, hists); c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); dim = c2c_fmt->dim; if (dim->se) { text = dim->header.line[line].text; /* Use the last line from sort_entry if not defined. */ if (!text && line == hists->hpp_list->nr_header_lines - 1) text = dim->se->se_header; } else { text = dim->header.line[line].text; if (span) { if (*span) { (*span)--; return 0; } *span = dim->header.line[line].span; } } if (!text) text = ""; return scnprintf(hpp->buf, hpp->size, "%*s", width, text); } /* * Return the estimated total cycles for a c2c_hist_entry * (rmt_hitm + lcl_hitm + rmt_peer + lcl_peer + other loads). */ static u64 c2c_hist_entry__cycles(struct c2c_hist_entry *c2c_he) { struct compute_stats *cs = &c2c_he->cstats; double cycles = 0; /* * compute_stats() in builtin-c2c.c routes each load sample into exactly * one cstats bucket (rmt_hitm, lcl_hitm, rmt_peer, lcl_peer or plain * load), so each bucket's cycle total is its mean times its own sample * count. Summing the per-bucket totals avoids both dropping peer-snoop * cycles and double counting a sample that carries several data-source * flags (e.g. Arm SPE sets HITM and PEER on the same load), which would * happen if the mean were multiplied by the non-exclusive stats counts. */ cycles += avg_stats(&cs->rmt_hitm) * cs->rmt_hitm.n; cycles += avg_stats(&cs->lcl_hitm) * cs->lcl_hitm.n; cycles += avg_stats(&cs->rmt_peer) * cs->rmt_peer.n; cycles += avg_stats(&cs->lcl_peer) * cs->lcl_peer.n; cycles += avg_stats(&cs->load) * cs->load.n; return (u64)cycles; } /* Sum c2c_hist_entry__cycles() across all level-1 entries. */ static u64 c2c_ext__total_cycles(void) { struct rb_node *nd; u64 total = 0; for (nd = rb_first_cached(&c2c_ext.function_hists.hists.entries); nd; nd = rb_next(nd)) { struct c2c_hist_entry *c2c_he = rb_entry(nd, struct c2c_hist_entry, he.rb_node); total += c2c_hist_entry__cycles(c2c_he); } return total; } /* * Store count shown in the column: a level-3 cacheline leaf shows its parent * level-2 writer's stores on that line, not all stores on the line. A level-2 * writer shows the sum across its level-3 cachelines. A level-1 reader shows * the sum across all included writers on the cachelines it reads; this is not * the reader function's own store count and is not additive across readers. */ static u64 hist_entry__displayed_stores(struct hist_entry *he) { struct c2c_hist_entry *c2c_he = container_of(he, struct c2c_hist_entry, he); struct rb_node *nd; u64 stores = 0; /* Level-2/3 entries already aggregate the stores they represent. */ if (he->parent_he) return c2c_he->stats.store; for (nd = rb_first_cached(&he->hroot_out); nd; nd = rb_next(nd)) { struct c2c_hist_entry *child_c2c = rb_entry(nd, struct c2c_hist_entry, he.rb_node); stores += child_c2c->stats.store; } return stores; } static int total_stores_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hist_entry *he) { int width = c2c_width(fmt, hpp, he->hists); u64 total = hist_entry__displayed_stores(he); return scnprintf(hpp->buf, hpp->size, "%*" PRIu64, width, total); } /* * symbol_view_entry - Render the unified, indented identity column. * * All three levels share this single column so the hierarchy reads top-down * with progressive indentation, like the normal report hierarchy view. It is * a function-centric view with no dedicated code-address column. Verbose * function rows can still include a representative address: * L1 read-side function: "- [k] cpupri_set" * L2 writing function: " - [k] pull_rt_task" * L3 shared cacheline: " 0xff2d0082809da080" */ static int symbol_view_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hist_entry *he) { int width = c2c_width(fmt, hpp, he->hists); int text_width; int ret; char folded_sign; ret = hist_entry__indent(he, hpp->buf, hpp->size); folded_sign = he->has_children ? (he->unfolded ? '-' : '+') : ' '; ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%c ", folded_sign); text_width = width - ret; if (text_width <= 0) return ret; if (hist_entry__is_cacheline(he)) { /* Level 3: the shared cacheline address. */ u64 addr = he->mem_info ? cl_address(mem_info__daddr(he->mem_info)->addr, chk_double_cl) : 0; char symbuf[32]; scnprintf(symbuf, sizeof(symbuf), "0x%" PRIx64, addr); ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%-*.*s", text_width, text_width, symbuf); } else { /* Level 1 and level 2 are both functions. */ size_t cell_size; int len; if ((size_t)ret >= hpp->size) return ret; cell_size = min_t(size_t, hpp->size - ret, (size_t)text_width + 1); len = sort_sym.se_snprintf(he, hpp->buf + ret, cell_size, text_width); /* * se_snprintf() accumulates repsep_snprintf() calls, which cap * their return at the remaining size - 1 rather than reporting * what the format would have needed, so len stays below * cell_size. Clamp anyway so ret cannot leave hpp->buf. */ if (len < 0) len = 0; else len = min_t(size_t, len, cell_size - 1); ret += len; if (len < text_width) ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%*s", text_width - len, ""); } return ret; } /* * cycles_percent_entry - Render cycles percentage column */ static int cycles_percent_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hist_entry *he) { struct c2c_hist_entry *c2c_he; int width = c2c_width(fmt, hpp, he->hists); u64 fn_cycles, total_cycles; char folded_sign; double pct; int ret, pct_width; /* Hide Cycles Percent for child functions and cachelines. */ if (he->parent_he) return scnprintf(hpp->buf, hpp->size, "%*s", width, ""); c2c_he = container_of(he, struct c2c_hist_entry, he); fn_cycles = c2c_hist_entry__cycles(c2c_he); /* Populated by c2c_function__build() once the L1 tree is built. */ total_cycles = c2c_ext.total_cycles; pct = total_cycles > 0 ? (double)fn_cycles / total_cycles * 100.0 : 0.0; /* 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[] = { &dim_cycles_percent, &dim_total_stores, &dim_symbol_view, NULL, }; static struct c2c_dimension *get_function_dimension(const char *name) { unsigned int i; for (i = 0; function_view_dimensions[i]; i++) { struct c2c_dimension *dim = function_view_dimensions[i]; if (!strcmp(dim->name, name)) return dim; } return NULL; } /* Wrappers so sort_entry-backed dimensions sort/collapse via their se. */ static int64_t c2c_se_cmp(struct perf_hpp_fmt *fmt, struct hist_entry *a, struct hist_entry *b) { struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); struct c2c_dimension *dim = c2c_fmt->dim; return dim->se->se_cmp(a, b); } static int64_t c2c_se_collapse(struct perf_hpp_fmt *fmt, struct hist_entry *a, struct hist_entry *b) { struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); struct c2c_dimension *dim = c2c_fmt->dim; int64_t (*collapse_fn)(struct hist_entry *a, struct hist_entry *b); collapse_fn = dim->se->se_collapse ?: dim->se->se_cmp; return collapse_fn(a, b); } static int64_t c2c_se_sort(struct perf_hpp_fmt *fmt, struct hist_entry *a, struct hist_entry *b) { struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); struct c2c_dimension *dim = c2c_fmt->dim; int64_t (*sort_fn)(struct hist_entry *a, struct hist_entry *b); sort_fn = dim->se->se_sort ?: dim->se->se_cmp; return sort_fn(a, b); } /* * Build the c2c_fmt for @name. Returns: * 0 and *fmtp set on success; * -ENOENT and *fmtp = NULL if @name is not a function-view dimension; * -ENOMEM if allocation failed (distinct from -ENOENT so * the caller does not misreport it as an * "invalid field"). */ static int get_function_format(const char *name, struct c2c_fmt **fmtp) {