| 1 | |
| 2 | |
| 3 | * C2C function model - function-level cacheline sharing analysis
|
| 4 | *
|
| 5 | * Displays a 3-level hierarchy showing which functions share cachelines:
|
| 6 | * Level 1: Read-side functions sorted by Cycles % (estimated load cycles)
|
| 7 | * Level 2: Functions sampled writing the shared lines read by level 1
|
| 8 | * Level 3: The specific cachelines where the two functions contend
|
| 9 | *
|
| 10 | * Builds the hierarchy from the existing cacheline histograms
|
| 11 | * (c2c_hist_entry->hists), reusing the shared c2c data structures.
|
| 12 |
|
| 13 |
|
| 14 | #include <errno.h>
|
| 15 | #include <inttypes.h>
|
| 16 | #include <stdlib.h>
|
| 17 | #include <string.h>
|
| 18 | #include <tools/libc_compat.h>
|
| 19 | #include <linux/list.h>
|
| 20 | #include <linux/rbtree.h>
|
| 21 | #include <linux/zalloc.h>
|
| 22 |
|
| 23 | #include "addr_location.h"
|
| 24 | #include "c2c.h"
|
| 25 | #include "cacheline.h"
|
| 26 | #include "debug.h"
|
| 27 | #include "dso.h"
|
| 28 | #include "hist.h"
|
| 29 | #include "map.h"
|
| 30 | #include "mem-events.h"
|
| 31 | #include "mem-info.h"
|
| 32 | #include "sort.h"
|
| 33 | #include "symbol.h"
|
| 34 | #include "thread.h"
|
| 35 |
|
| 36 | struct c2c_function_model {
|
| 37 | struct c2c_hists function_hists;
|
| 38 |
|
| 39 | u64 total_cycles;
|
| 40 |
|
| 41 | struct c2c_hists *cl_hists;
|
| 42 |
|
| 43 | const char *cl_sort;
|
| 44 |
|
| 45 | bool symbol_full;
|
| 46 | };
|
| 47 |
|
| 48 | static struct c2c_function_model c2c_ext;
|
| 49 |
|
| 50 | static inline u64 c2c_hitm_count(const struct c2c_stats *stats)
|
| 51 | {
|
| 52 | return stats->tot_hitm;
|
| 53 | }
|
| 54 |
|
| 55 | static int64_t c2c_function_cmp(const struct map_symbol *left,
|
| 56 | const struct map_symbol *right)
|
| 57 | {
|
| 58 | const struct dso *left_dso = left->map ? map__dso(left->map) : NULL;
|
| 59 | const struct dso *right_dso = right->map ? map__dso(right->map) : NULL;
|
| 60 | int ret;
|
| 61 |
|
| 62 | if (!left_dso || !right_dso) {
|
| 63 | if (left_dso != right_dso)
|
| 64 | return left_dso ? 1 : -1;
|
| 65 | } else {
|
| 66 | |
| 67 | * Use the same DSO name as _sort__dso_cmp() (short name unless
|
| 68 | * verbose), so this matches the DSO comparison the level-1
|
| 69 | * entries are deduplicated by; otherwise same-basename DSOs
|
| 70 | * could be split or merged inconsistently across levels.
|
| 71 |
|
| 72 | const char *left_name = verbose > 0 ?
|
| 73 | dso__long_name(left_dso) : dso__short_name(left_dso);
|
| 74 | const char *right_name = verbose > 0 ?
|
| 75 | dso__long_name(right_dso) : dso__short_name(right_dso);
|
| 76 |
|
| 77 | ret = strcmp(left_name, right_name);
|
| 78 | if (ret)
|
| 79 | return ret;
|
| 80 | }
|
| 81 |
|
| 82 | return _sort__sym_cmp(left->sym, right->sym);
|
| 83 | }
|
| 84 |
|
| 85 | static inline u64 hist_entry__iaddr(struct hist_entry *he)
|
| 86 | {
|
| 87 | if (he->mem_info)
|
| 88 | return mem_info__iaddr(he->mem_info)->addr;
|
| 89 | return he->ip;
|
| 90 | }
|
| 91 |
|
| 92 | |
| 93 | * Hierarchy levels (by depth): L1 = read-side function, L2 = the writing
|
| 94 | * function it contends with, L3 = the specific shared cacheline.
|
| 95 |
|
| 96 | static inline bool hist_entry__is_cacheline(struct hist_entry *he)
|
| 97 | {
|
| 98 | return he->parent_he && he->parent_he->parent_he;
|
| 99 | }
|
| 100 |
|
| 101 |
|
| 102 | #define C2C_FUNC_INDENT 2
|
| 103 |
|
| 104 |
|
| 105 | #define C2C_FUNC_FOLD_WIDTH 2
|
| 106 |
|
| 107 | |
| 108 | * Write he->depth levels of leading indentation into @buf, so lower-level
|
| 109 | * entries are visually nested under their parent. Returns bytes written.
|
| 110 |
|
| 111 | static int hist_entry__indent(struct hist_entry *he, char *buf, size_t size)
|
| 112 | {
|
| 113 | int indent = he->depth * C2C_FUNC_INDENT;
|
| 114 |
|
| 115 | if (indent <= 0 || (size_t)indent >= size)
|
| 116 | return 0;
|
| 117 |
|
| 118 | return scnprintf(buf, size, "%*s", indent, "");
|
| 119 | }
|
| 120 |
|
| 121 | static int symbol_width(struct hists *hists, struct sort_entry *se)
|
| 122 | {
|
| 123 | int width = hists__col_len(hists, se->se_width_idx);
|
| 124 |
|
| 125 | |
| 126 | * Cap long symbol names as the cacheline view does. The stored column
|
| 127 | * length is grown up front to fit the deepest, longest identity cell
|
| 128 | * (including a level-3 cacheline address), so this cap never shrinks the
|
| 129 | * column below what the cacheline address needs.
|
| 130 |
|
| 131 | if (!c2c_ext.symbol_full && width > SYMBOL_WIDTH)
|
| 132 | width = SYMBOL_WIDTH;
|
| 133 |
|
| 134 | return width;
|
| 135 | }
|
| 136 |
|
| 137 | static struct c2c_dimension dim_symbol_view;
|
| 138 |
|
| 139 | |
| 140 | * c2c_width - Calculate width for a C2C column in function view
|
| 141 |
|
| 142 | static int c2c_width(struct perf_hpp_fmt *fmt,
|
| 143 | struct perf_hpp *hpp __maybe_unused,
|
| 144 | struct hists *hists)
|
| 145 | {
|
| 146 | struct c2c_fmt *c2c_fmt;
|
| 147 | struct c2c_dimension *dim;
|
| 148 |
|
| 149 | c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
| 150 | dim = c2c_fmt->dim;
|
| 151 |
|
| 152 | if (dim == &dim_symbol_view)
|
| 153 | return symbol_width(hists, dim->se);
|
| 154 |
|
| 155 | return dim->se ? hists__col_len(hists, dim->se->se_width_idx) :
|
| 156 | dim->width;
|
| 157 | }
|
| 158 |
|
| 159 | static int c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
| 160 | struct hists *hists, int line, int *span)
|
| 161 | {
|
| 162 | struct c2c_fmt *c2c_fmt;
|
| 163 | struct c2c_dimension *dim;
|
| 164 | const char *text = NULL;
|
| 165 | int width = c2c_width(fmt, hpp, hists);
|
| 166 |
|
| 167 | c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
| 168 | dim = c2c_fmt->dim;
|
| 169 |
|
| 170 | if (dim->se) {
|
| 171 | text = dim->header.line[line].text;
|
| 172 |
|
| 173 | if (!text && line == hists->hpp_list->nr_header_lines - 1)
|
| 174 | text = dim->se->se_header;
|
| 175 | } else {
|
| 176 | text = dim->header.line[line].text;
|
| 177 |
|
| 178 | if (span) {
|
| 179 | if (*span) {
|
| 180 | (*span)--;
|
| 181 | return 0;
|
| 182 | }
|
| 183 |
|
| 184 | *span = dim->header.line[line].span;
|
| 185 | }
|
| 186 | }
|
| 187 |
|
| 188 | if (!text)
|
| 189 | text = "";
|
| 190 |
|
| 191 | return scnprintf(hpp->buf, hpp->size, "%*s", width, text);
|
| 192 | }
|
| 193 |
|
| 194 | |
| 195 | * Return the estimated total cycles for a c2c_hist_entry
|
| 196 | * (rmt_hitm + lcl_hitm + rmt_peer + lcl_peer + other loads).
|
| 197 |
|
| 198 | static u64 c2c_hist_entry__cycles(struct c2c_hist_entry *c2c_he)
|
| 199 | {
|
| 200 | struct compute_stats *cs = &c2c_he->cstats;
|
| 201 | double cycles = 0;
|
| 202 |
|
| 203 | |
| 204 | * compute_stats() in builtin-c2c.c routes each load sample into exactly
|
| 205 | * one cstats bucket (rmt_hitm, lcl_hitm, rmt_peer, lcl_peer or plain
|
| 206 | * load), so each bucket's cycle total is its mean times its own sample
|
| 207 | * count. Summing the per-bucket totals avoids both dropping peer-snoop
|
| 208 | * cycles and double counting a sample that carries several data-source
|
| 209 | * flags (e.g. Arm SPE sets HITM and PEER on the same load), which would
|
| 210 | * happen if the mean were multiplied by the non-exclusive stats counts.
|
| 211 |
|
| 212 | cycles += avg_stats(&cs->rmt_hitm) * cs->rmt_hitm.n;
|
| 213 | cycles += avg_stats(&cs->lcl_hitm) * cs->lcl_hitm.n;
|
| 214 | cycles += avg_stats(&cs->rmt_peer) * cs->rmt_peer.n;
|
| 215 | cycles += avg_stats(&cs->lcl_peer) * cs->lcl_peer.n;
|
| 216 | cycles += avg_stats(&cs->load) * cs->load.n;
|
| 217 |
|
| 218 | return (u64)cycles;
|
| 219 | }
|
| 220 |
|
| 221 |
|
| 222 | static u64 c2c_ext__total_cycles(void)
|
| 223 | {
|
| 224 | struct rb_node *nd;
|
| 225 | u64 total = 0;
|
| 226 |
|
| 227 | for (nd = rb_first_cached(&c2c_ext.function_hists.hists.entries); nd;
|
| 228 | nd = rb_next(nd)) {
|
| 229 | struct c2c_hist_entry *c2c_he =
|
| 230 | rb_entry(nd, struct c2c_hist_entry, he.rb_node);
|
| 231 |
|
| 232 | total += c2c_hist_entry__cycles(c2c_he);
|
| 233 | }
|
| 234 | return total;
|
| 235 | }
|
| 236 |
|
| 237 | |
| 238 | * Store count shown in the column: a level-3 cacheline leaf shows its parent
|
| 239 | * level-2 writer's stores on that line, not all stores on the line. A level-2
|
| 240 | * writer shows the sum across its level-3 cachelines. A level-1 reader shows
|
| 241 | * the sum across all included writers on the cachelines it reads; this is not
|
| 242 | * the reader function's own store count and is not additive across readers.
|
| 243 |
|
| 244 | static u64 hist_entry__displayed_stores(struct hist_entry *he)
|
| 245 | {
|
| 246 | struct c2c_hist_entry *c2c_he = container_of(he, struct c2c_hist_entry, he);
|
| 247 | struct rb_node *nd;
|
| 248 | u64 stores = 0;
|
| 249 |
|
| 250 |
|
| 251 | if (he->parent_he)
|
| 252 | return c2c_he->stats.store;
|
| 253 |
|
| 254 | for (nd = rb_first_cached(&he->hroot_out); nd; nd = rb_next(nd)) {
|
| 255 | struct c2c_hist_entry *child_c2c =
|
| 256 | rb_entry(nd, struct c2c_hist_entry, he.rb_node);
|
| 257 | stores += child_c2c->stats.store;
|
| 258 | }
|
| 259 | return stores;
|
| 260 | }
|
| 261 |
|
| 262 | static int
|
| 263 | total_stores_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
| 264 | struct hist_entry *he)
|
| 265 | {
|
| 266 | int width = c2c_width(fmt, hpp, he->hists);
|
| 267 | u64 total = hist_entry__displayed_stores(he);
|
| 268 |
|
| 269 | return scnprintf(hpp->buf, hpp->size, "%*" PRIu64, width, total);
|
| 270 | }
|
| 271 |
|
| 272 | |
| 273 | * symbol_view_entry - Render the unified, indented identity column.
|
| 274 | *
|
| 275 | * All three levels share this single column so the hierarchy reads top-down
|
| 276 | * with progressive indentation, like the normal report hierarchy view. It is
|
| 277 | * a function-centric view with no dedicated code-address column. Verbose
|
| 278 | * function rows can still include a representative address:
|
| 279 | * L1 read-side function: "- [k] cpupri_set"
|
| 280 | * L2 writing function: " - [k] pull_rt_task"
|
| 281 | * L3 shared cacheline: " 0xff2d0082809da080"
|
| 282 |
|
| 283 | static int
|
| 284 | symbol_view_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
| 285 | struct hist_entry *he)
|
| 286 | {
|
| 287 | int width = c2c_width(fmt, hpp, he->hists);
|
| 288 | int text_width;
|
| 289 | int ret;
|
| 290 | char folded_sign;
|
| 291 |
|
| 292 | ret = hist_entry__indent(he, hpp->buf, hpp->size);
|
| 293 |
|
| 294 | folded_sign = he->has_children ? (he->unfolded ? '-' : '+') : ' ';
|
| 295 | ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%c ", folded_sign);
|
| 296 |
|
| 297 | text_width = width - ret;
|
| 298 | if (text_width <= 0)
|
| 299 | return ret;
|
| 300 |
|
| 301 | if (hist_entry__is_cacheline(he)) {
|
| 302 |
|
| 303 | u64 addr = he->mem_info ?
|
| 304 | cl_address(mem_info__daddr(he->mem_info)->addr, chk_double_cl) : 0;
|
| 305 | char symbuf[32];
|
| 306 |
|
| 307 | scnprintf(symbuf, sizeof(symbuf), "0x%" PRIx64, addr);
|
| 308 | ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%-*.*s",
|
| 309 | text_width, text_width, symbuf);
|
| 310 | } else {
|
| 311 |
|
| 312 | size_t cell_size;
|
| 313 | int len;
|
| 314 |
|
| 315 | if ((size_t)ret >= hpp->size)
|
| 316 | return ret;
|
| 317 |
|
| 318 | cell_size = min_t(size_t, hpp->size - ret,
|
| 319 | (size_t)text_width + 1);
|
| 320 | len = sort_sym.se_snprintf(he, hpp->buf + ret, cell_size,
|
| 321 | text_width);
|
| 322 | |
| 323 | * se_snprintf() accumulates repsep_snprintf() calls, which cap
|
| 324 | * their return at the remaining size - 1 rather than reporting
|
| 325 | * what the format would have needed, so len stays below
|
| 326 | * cell_size. Clamp anyway so ret cannot leave hpp->buf.
|
| 327 |
|
| 328 | if (len < 0)
|
| 329 | len = 0;
|
| 330 | else
|
| 331 | len = min_t(size_t, len, cell_size - 1);
|
| 332 |
|
| 333 | ret += len;
|
| 334 | if (len < text_width)
|
| 335 | ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%*s",
|
| 336 | text_width - len, "");
|
| 337 | }
|
| 338 |
|
| 339 | return ret;
|
| 340 | }
|
| 341 |
|
| 342 | |
| 343 | * cycles_percent_entry - Render cycles percentage column
|
| 344 |
|
| 345 | static int
|
| 346 | cycles_percent_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
| 347 | struct hist_entry *he)
|
| 348 | {
|
| 349 | struct c2c_hist_entry *c2c_he;
|
| 350 | int width = c2c_width(fmt, hpp, he->hists);
|
| 351 | u64 fn_cycles, total_cycles;
|
| 352 | char folded_sign;
|
| 353 | double pct;
|
| 354 | int ret, pct_width;
|
| 355 |
|
| 356 |
|
| 357 | if (he->parent_he)
|
| 358 | return scnprintf(hpp->buf, hpp->size, "%*s", width, "");
|
| 359 |
|
| 360 | c2c_he = container_of(he, struct c2c_hist_entry, he);
|
| 361 | fn_cycles = c2c_hist_entry__cycles(c2c_he);
|
| 362 |
|
| 363 | total_cycles = c2c_ext.total_cycles;
|
| 364 | pct = total_cycles > 0 ? (double)fn_cycles / total_cycles * 100.0 : 0.0;
|
| 365 |
|
| 366 |
|
| 367 | folded_sign = he->has_children ? (he->unfolded ? '-' : '+') : ' ';
|
| 368 | ret = scnprintf(hpp->buf, hpp->size, "%c ", folded_sign);
|
| 369 |
|
| 370 | pct_width = width - ret;
|
| 371 | if (pct_width <= 0)
|
| 372 | return ret;
|
| 373 | ret += scnprintf(hpp->buf + ret, hpp->size - ret, "%*.2f%%", pct_width - 1, pct);
|
| 374 | return ret;
|
| 375 | }
|
| 376 |
|
| 377 | |
| 378 | * cycles_percent_cmp - Comparison function for cycles percentage sorting
|
| 379 |
|
| 380 | static int64_t
|
| 381 | cycles_percent_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
|
| 382 | struct hist_entry *left, struct hist_entry *right)
|
| 383 | {
|
| 384 | struct c2c_hist_entry *c2c_left = container_of(left, struct c2c_hist_entry, he);
|
| 385 | struct c2c_hist_entry *c2c_right = container_of(right, struct c2c_hist_entry, he);
|
| 386 | u64 cycles_left, cycles_right;
|
| 387 |
|
| 388 |
|
| 389 | if (left->parent_he || right->parent_he)
|
| 390 | return 0;
|
| 391 |
|
| 392 | cycles_left = c2c_hist_entry__cycles(c2c_left);
|
| 393 | cycles_right = c2c_hist_entry__cycles(c2c_right);
|
| 394 |
|
| 395 | return (cycles_left > cycles_right) - (cycles_left < cycles_right);
|
| 396 | }
|
| 397 |
|
| 398 | |
| 399 | * total_stores_cmp - Comparison function for total stores sorting
|
| 400 |
|
| 401 | static int64_t
|
| 402 | total_stores_cmp(struct perf_hpp_fmt *fmt __maybe_unused,
|
| 403 | struct hist_entry *left, struct hist_entry *right)
|
| 404 | {
|
| 405 | u64 left_store = hist_entry__displayed_stores(left);
|
| 406 | u64 right_store = hist_entry__displayed_stores(right);
|
| 407 |
|
| 408 | return (left_store > right_store) - (left_store < right_store);
|
| 409 | }
|
| 410 |
|
| 411 | |
| 412 | * Function view dimensions
|
| 413 |
|
| 414 | static struct c2c_dimension dim_cycles_percent = {
|
| 415 | .header = HEADER_BOTH("Cycles", "%"),
|
| 416 | .name = "cycles_percent",
|
| 417 | .cmp = cycles_percent_cmp,
|
| 418 | .entry = cycles_percent_entry,
|
| 419 | .width = 9,
|
| 420 | };
|
| 421 |
|
| 422 | static struct c2c_dimension dim_total_stores = {
|
| 423 | .header = HEADER_BOTH("Store", "count"),
|
| 424 | .name = "total_stores",
|
| 425 | .cmp = total_stores_cmp,
|
| 426 | .entry = total_stores_entry,
|
| 427 | .width = 7,
|
| 428 | };
|
| 429 |
|
| 430 | static struct c2c_dimension dim_symbol_view = {
|
| 431 | .header = HEADER_LOW("Function / Contending function / Cacheline"),
|
| 432 | .name = "symbol_view",
|
| 433 | .se = &sort_sym,
|
| 434 | .entry = symbol_view_entry,
|
| 435 | .width = SYMBOL_WIDTH,
|
| 436 | };
|
| 437 |
|
| 438 | static struct c2c_dimension *function_view_dimensions[] = {
|
| 439 | &dim_cycles_percent,
|
| 440 | &dim_total_stores,
|
| 441 | &dim_symbol_view,
|
| 442 | NULL,
|
| 443 | };
|
| 444 |
|
| 445 | static struct c2c_dimension *get_function_dimension(const char *name)
|
| 446 | {
|
| 447 | unsigned int i;
|
| 448 |
|
| 449 | for (i = 0; function_view_dimensions[i]; i++) {
|
| 450 | struct c2c_dimension *dim = function_view_dimensions[i];
|
| 451 |
|
| 452 | if (!strcmp(dim->name, name))
|
| 453 | return dim;
|
| 454 | }
|
| 455 |
|
| 456 | return NULL;
|
| 457 | }
|
| 458 |
|
| 459 |
|
| 460 | static int64_t c2c_se_cmp(struct perf_hpp_fmt *fmt,
|
| 461 | struct hist_entry *a, struct hist_entry *b)
|
| 462 | {
|
| 463 | struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
| 464 | struct c2c_dimension *dim = c2c_fmt->dim;
|
| 465 |
|
| 466 | return dim->se->se_cmp(a, b);
|
| 467 | }
|
| 468 |
|
| 469 | static int64_t c2c_se_collapse(struct perf_hpp_fmt *fmt,
|
| 470 | struct hist_entry *a, struct hist_entry *b)
|
| 471 | {
|
| 472 | struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
| 473 | struct c2c_dimension *dim = c2c_fmt->dim;
|
| 474 | int64_t (*collapse_fn)(struct hist_entry *a, struct hist_entry *b);
|
| 475 |
|
| 476 | collapse_fn = dim->se->se_collapse ?: dim->se->se_cmp;
|
| 477 | return collapse_fn(a, b);
|
| 478 | }
|
| 479 |
|
| 480 | static int64_t c2c_se_sort(struct perf_hpp_fmt *fmt,
|
| 481 | struct hist_entry *a, struct hist_entry *b)
|
| 482 | {
|
| 483 | struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
| 484 | struct c2c_dimension *dim = c2c_fmt->dim;
|
| 485 | int64_t (*sort_fn)(struct hist_entry *a, struct hist_entry *b);
|
| 486 |
|
| 487 | sort_fn = dim->se->se_sort ?: dim->se->se_cmp;
|
| 488 | return sort_fn(a, b);
|
| 489 | }
|
| 490 |
|
| 491 | |
| 492 | * Build the c2c_fmt for @name. Returns:
|
| 493 | * 0 and *fmtp set on success;
|
| 494 | * -ENOENT and *fmtp = NULL if @name is not a function-view dimension;
|
| 495 | * -ENOMEM if allocation failed (distinct from -ENOENT so
|
| 496 | * the caller does not misreport it as an
|
| 497 | * "invalid field").
|
| 498 |
|
| 499 | static int get_function_format(const char *name, struct c2c_fmt **fmtp)
|
| 500 | {
|
| 501 | struct c2c_dimension *dim = get_function_dimension(name);
|
| 502 | struct c2c_fmt *c2c_fmt;
|
| 503 | struct perf_hpp_fmt *fmt;
|
| 504 |
|
| 505 | *fmtp = NULL;
|
| 506 |
|
| 507 | if (!dim)
|
| 508 | return -ENOENT;
|
| 509 |
|
| 510 | c2c_fmt = zalloc(sizeof(*c2c_fmt));
|
| 511 | if (!c2c_fmt)
|
| 512 | return -ENOMEM;
|
| 513 |
|
| 514 | fmt = &c2c_fmt->fmt;
|
| 515 |
|
| 516 | c2c_fmt->dim = dim;
|
| 517 | INIT_LIST_HEAD(&fmt->list);
|
| 518 | INIT_LIST_HEAD(&fmt->sort_list);
|
| 519 |
|
| 520 | fmt->cmp = dim->se ? c2c_se_cmp : dim->cmp;
|
| 521 | fmt->sort = dim->se ? c2c_se_sort : dim->cmp;
|
| 522 | fmt->color = dim->color;
|
| 523 | fmt->entry = dim->entry;
|
| 524 | fmt->header = c2c_header;
|
| 525 | fmt->width = c2c_width;
|
| 526 | fmt->collapse = dim->se ? c2c_se_collapse : dim->cmp;
|
| 527 | fmt->equal = c2c_fmt_equal;
|
| 528 | fmt->free = c2c_fmt_free;
|
| 529 |
|
| 530 | *fmtp = c2c_fmt;
|
| 531 | return 0;
|
| 532 | }
|
| 533 |
|
| 534 | static int
|
| 535 | c2c_function_hists__init_output(struct perf_hpp_list *hpp_list, char *name,
|
| 536 | struct perf_env *env __maybe_unused)
|
| 537 | {
|
| 538 | struct c2c_fmt *c2c_fmt;
|
| 539 | int ret;
|
| 540 |
|
| 541 | ret = get_function_format(name, &c2c_fmt);
|
| 542 | if (ret == -ENOMEM)
|
| 543 | return ret;
|
| 544 |
|
| 545 | if (ret == -ENOENT)
|
| 546 | return -EINVAL;
|
| 547 |
|
| 548 | |
| 549 | * Mark symbol-backed columns so hists__has(hists, sym) is correct.
|
| 550 | * Only dim_symbol_view carries a sort_entry (.se); the function
|
| 551 | * view's field strings are fixed and always include symbol_view, so
|
| 552 | * this single check is sufficient (unlike the user-configurable
|
| 553 | * cacheline view, which must also test dim_iaddr).
|
| 554 |
|
| 555 | if (c2c_fmt->dim->se == &sort_sym)
|
| 556 | hpp_list->sym = 1;
|
| 557 |
|
| 558 | perf_hpp_list__column_register(hpp_list, &c2c_fmt->fmt);
|
| 559 | return 0;
|
| 560 | }
|
| 561 |
|
| 562 | static int
|
| 563 | c2c_function_hists__init_sort(struct perf_hpp_list *hpp_list, char *name,
|
| 564 | struct perf_env *env __maybe_unused)
|
| 565 | {
|
| 566 | struct c2c_fmt *c2c_fmt;
|
| 567 | int ret;
|
| 568 |
|
| 569 | ret = get_function_format(name, &c2c_fmt);
|
| 570 | if (ret == -ENOMEM)
|
| 571 | return ret;
|
| 572 |
|
| 573 | if (ret == -ENOENT)
|
| 574 | return -EINVAL;
|
| 575 |
|
| 576 |
|
| 577 | if (c2c_fmt->dim->se == &sort_sym)
|
| 578 | hpp_list->sym = 1;
|
| 579 |
|
| 580 | perf_hpp_list__register_sort_field(hpp_list, &c2c_fmt->fmt);
|
| 581 | return 0;
|
| 582 | }
|
| 583 |
|
| 584 | typedef int (*hpp_list_add_fn)(struct perf_hpp_list *hpp_list, char *name,
|
| 585 | struct perf_env *env);
|
| 586 |
|
| 587 | static int function_hpp_list__add_tokens(struct perf_hpp_list *hpp_list, char *list,
|
| 588 | struct perf_env *env, hpp_list_add_fn add)
|
| 589 | {
|
| 590 | char *tok, *tmp;
|
| 591 | int ret;
|
| 592 |
|
| 593 | if (!list)
|
| 594 | return 0;
|
| 595 |
|
| 596 | for (tok = strtok_r(list, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) {
|
| 597 | ret = add(hpp_list, tok, env);
|
| 598 | if (ret) {
|
| 599 | if (ret == -EINVAL || ret == -ESRCH)
|
| 600 | pr_err("Invalid c2c function-view field: %s\n", tok);
|
| 601 | return ret;
|
| 602 | }
|
| 603 | }
|
| 604 | return 0;
|
| 605 | }
|
| 606 |
|
| 607 | |
| 608 | * Append the function view's sort keys to its own output fields, mirroring
|
| 609 | * perf_hpp__setup_output_field() but on the local @list. The shared helper
|
| 610 | * registers onto the global perf_hpp_list, which would leave this local list
|
| 611 | * without output columns, so the function view keeps its own copy here.
|
| 612 |
|
| 613 | static void c2c_function_hists__setup_output_field(struct perf_hpp_list *list)
|
| 614 | {
|
| 615 | struct perf_hpp_fmt *fmt;
|
| 616 |
|
| 617 | perf_hpp_list__for_each_sort_list(list, fmt) {
|
| 618 | struct perf_hpp_fmt *pos;
|
| 619 |
|
| 620 | if (!fmt->entry && !fmt->color)
|
| 621 | continue;
|
| 622 |
|
| 623 | perf_hpp_list__for_each_format(list, pos) {
|
| 624 | if (c2c_fmt_equal(fmt, pos))
|
| 625 | goto next;
|
| 626 | }
|
| 627 |
|
| 628 | perf_hpp_list__column_register(list, fmt);
|
| 629 | next:
|
| 630 | continue;
|
| 631 | }
|
| 632 | }
|
| 633 |
|
| 634 | static int
|
| 635 | function_hpp_list__parse(struct perf_hpp_list *hpp_list,
|
| 636 | const char *output_str,
|
| 637 | const char *sort_str,
|
| 638 | struct perf_env *env)
|
| 639 | {
|
| 640 | char *output = output_str ? strdup(output_str) : NULL;
|
| 641 | char *sort = sort_str ? strdup(sort_str) : NULL;
|
| 642 | int ret = 0;
|
| 643 |
|
| 644 | if ((output_str && !output) || (sort_str && !sort)) {
|
| 645 | ret = -ENOMEM;
|
| 646 | goto out;
|
| 647 | }
|
| 648 |
|
| 649 | ret = function_hpp_list__add_tokens(hpp_list, output, env,
|
| 650 | c2c_function_hists__init_output);
|
| 651 | if (ret)
|
| 652 | goto out;
|
| 653 |
|
| 654 | ret = function_hpp_list__add_tokens(hpp_list, sort, env,
|
| 655 | c2c_function_hists__init_sort);
|
| 656 | if (ret)
|
| 657 | goto out;
|
| 658 |
|
| 659 | c2c_function_hists__setup_output_field(hpp_list);
|
| 660 | out:
|
| 661 | if (ret)
|
| 662 | perf_hpp__reset_output_field(hpp_list);
|
| 663 | free(output);
|
| 664 | free(sort);
|
| 665 | return ret;
|
| 666 | }
|
| 667 |
|
| 668 | static int
|
| 669 | c2c_function_hists__init(struct c2c_hists *hists,
|
| 670 | const char *sort,
|
| 671 | int nr_header_lines,
|
| 672 | struct perf_env *env)
|
| 673 | {
|
| 674 | __hists__init(&hists->hists, &hists->list);
|
| 675 |
|
| 676 | perf_hpp_list__init(&hists->list);
|
| 677 |
|
| 678 | hists->list.nr_header_lines = nr_header_lines;
|
| 679 |
|
| 680 | return function_hpp_list__parse(&hists->list, NULL, sort, env);
|
| 681 | }
|
| 682 |
|
| 683 | static int
|
| 684 | c2c_function_hists__reinit(struct c2c_hists *c2c_hists,
|
| 685 | const char *output,
|
| 686 | const char *sort,
|
| 687 | struct perf_env *env)
|
| 688 | {
|
| 689 | int nr_header_lines = c2c_hists->list.nr_header_lines;
|
| 690 |
|
| 691 | perf_hpp__reset_output_field(&c2c_hists->list);
|
| 692 |
|
| 693 |
|
| 694 | c2c_hists->list.need_collapse = 0;
|
| 695 | c2c_hists->list.parent = 0;
|
| 696 | c2c_hists->list.sym = 0;
|
| 697 | c2c_hists->list.dso = 0;
|
| 698 | c2c_hists->list.socket = 0;
|
| 699 | c2c_hists->list.thread = 0;
|
| 700 | c2c_hists->list.comm = 0;
|
| 701 | c2c_hists->list.comm_nodigit = 0;
|
| 702 | c2c_hists->list.nr_header_lines = nr_header_lines;
|
| 703 |
|
| 704 | return function_hpp_list__parse(&c2c_hists->list, output, sort, env);
|
| 705 | }
|
| 706 |
|
| 707 |
|
| 708 | static void c2c_stats_merge(struct stats *dest, const struct stats *src)
|
| 709 | {
|
| 710 | double delta;
|
| 711 |
|
| 712 | if (src->n == 0)
|
| 713 | return;
|
| 714 |
|
| 715 | if (dest->n == 0) {
|
| 716 | *dest = *src;
|
| 717 | return;
|
| 718 | }
|
| 719 |
|
| 720 | delta = src->mean - dest->mean;
|
| 721 | dest->M2 += src->M2 + delta * delta * dest->n * src->n / (dest->n + src->n);
|
| 722 | dest->mean = (dest->mean * dest->n + src->mean * src->n) / (dest->n + src->n);
|
| 723 | dest->n += src->n;
|
| 724 |
|
| 725 |
|
| 726 | if (src->max > dest->max)
|
| 727 | dest->max = src->max;
|
| 728 | if (src->min < dest->min)
|
| 729 | dest->min = src->min;
|
| 730 | }
|
| 731 |
|
| 732 |
|
| 733 | static void c2c_add_cstats(struct compute_stats *dest,
|
| 734 | const struct compute_stats *src)
|
| 735 | {
|
| 736 | c2c_stats_merge(&dest->rmt_hitm, &src->rmt_hitm);
|
| 737 | c2c_stats_merge(&dest->lcl_hitm, &src->lcl_hitm);
|
| 738 | c2c_stats_merge(&dest->rmt_peer, &src->rmt_peer);
|
| 739 | c2c_stats_merge(&dest->lcl_peer, &src->lcl_peer);
|
| 740 | c2c_stats_merge(&dest->load, &src->load);
|
| 741 | }
|
| 742 |
|
| 743 | static bool hist_entry__add_c2c_stats(struct hist_entry *he,
|
| 744 | const struct c2c_stats *stats)
|
| 745 | {
|
| 746 | u64 nr_events = c2c_hitm_count(stats) + stats->rmt_peer + stats->lcl_peer;
|
| 747 | u64 weight1 = c2c_hitm_count(stats);
|
| 748 |
|
| 749 | |
| 750 | * Allocate before touching he->stat, so a failure here leaves the
|
| 751 | * entry unmodified and the caller can bail out without having
|
| 752 | * half-updated the statistics.
|
| 753 |
|
| 754 | if (symbol_conf.cumulate_callchain && !he->stat_acc) {
|
| 755 | he->stat_acc = calloc(1, sizeof(struct he_stat));
|
| 756 | if (!he->stat_acc)
|
| 757 | return false;
|
| 758 | }
|
| 759 |
|
| 760 | he->stat.nr_events += nr_events;
|
| 761 | he->stat.period += nr_events;
|
| 762 | he->stat.weight1 += weight1;
|
| 763 |
|
| 764 | if (!symbol_conf.cumulate_callchain)
|
| 765 | return true;
|
| 766 |
|
| 767 | he->stat_acc->nr_events += nr_events;
|
| 768 | he->stat_acc->period += nr_events;
|
| 769 | he->stat_acc->weight1 += weight1;
|
| 770 |
|
| 771 | return true;
|
| 772 | }
|
| 773 |
|
| 774 | static void c2c_he__free_hierarchy(struct hist_entry *he);
|
| 775 |
|
| 776 | |
| 777 | * Free a function-view histogram entry (hist_entry_ops::free).
|
| 778 |
|
| 779 | static void c2c_function_he_free(void *ptr)
|
| 780 | {
|
| 781 | struct hist_entry *he = ptr;
|
| 782 | struct c2c_hist_entry *c2c_he;
|
| 783 |
|
| 784 | c2c_he = container_of(he, struct c2c_hist_entry, he);
|
| 785 |
|
| 786 | if (c2c_he->hists) {
|
| 787 | perf_hpp__reset_output_field(&c2c_he->hists->list);
|
| 788 | hists__delete_all_entries(&c2c_he->hists->hists);
|
| 789 | zfree(&c2c_he->hists);
|
| 790 | }
|
| 791 |
|
| 792 | c2c_he__free_hierarchy(he);
|
| 793 |
|
| 794 | free(c2c_he);
|
| 795 | }
|
| 796 |
|
| 797 | static void c2c_he__free_hierarchy(struct hist_entry *he)
|
| 798 | {
|
| 799 | struct rb_node *nd;
|
| 800 | struct hist_entry *child_he;
|
| 801 |
|
| 802 | |
| 803 | * A leaf entry stores its callchains in the sorted_chain member, which
|
| 804 | * shares a union with the hroot_in/hroot_out child trees, so its
|
| 805 | * hroot_out is not a valid subtree to walk. Leaf entries never have a
|
| 806 | * child hierarchy here, so stop before touching hroot_out.
|
| 807 |
|
| 808 | if (he->leaf)
|
| 809 | return;
|
| 810 |
|
| 811 | if (RB_EMPTY_ROOT(&he->hroot_out.rb_root))
|
| 812 | return;
|
| 813 |
|
| 814 | nd = rb_first_cached(&he->hroot_out);
|
| 815 | while (nd) {
|
| 816 | struct rb_node *next = rb_next(nd);
|
| 817 |
|
| 818 | child_he = rb_entry(nd, struct hist_entry, rb_node);
|
| 819 | rb_erase_cached(&child_he->rb_node, &he->hroot_out);
|
| 820 | hist_entry__delete(child_he);
|
| 821 |
|
| 822 | nd = next;
|
| 823 | }
|
| 824 |
|
| 825 |
|
| 826 | he->hroot_out = RB_ROOT_CACHED;
|
| 827 | }
|
| 828 |
|
| 829 | |
| 830 | * Drop level-2 writing functions that carry no stores or
|
| 831 | * no cacheline children. Writers are only added when they store into a shared
|
| 832 | * line, so this is mainly a safety net. Returns the number of surviving
|
| 833 | * writers.
|
| 834 |
|
| 835 | static int c2c_he__prune_empty_writers(struct hist_entry *l1_he)
|
| 836 | {
|
| 837 | struct rb_node *nd;
|
| 838 | int surviving = 0;
|
| 839 |
|
| 840 | if (!l1_he->has_children)
|
| 841 | return 0;
|
| 842 |
|
| 843 | nd = rb_first_cached(&l1_he->hroot_out);
|
| 844 | while (nd) {
|
| 845 | struct rb_node *next = rb_next(nd);
|
| 846 | struct hist_entry *l2_he = rb_entry(nd, struct hist_entry, rb_node);
|
| 847 |
|
| 848 | if (l2_he->has_children && hist_entry__displayed_stores(l2_he) > 0) {
|
| 849 | surviving++;
|
| 850 | } else {
|
| 851 | rb_erase_cached(&l2_he->rb_node, &l1_he->hroot_out);
|
| 852 | hist_entry__delete(l2_he);
|
| 853 | }
|
| 854 | nd = next;
|
| 855 | }
|
| 856 |
|
| 857 | if (!surviving) {
|
| 858 | l1_he->hroot_out = RB_ROOT_CACHED;
|
| 859 | l1_he->has_children = false;
|
| 860 | l1_he->unfolded = false;
|
| 861 | }
|
| 862 | return surviving;
|
| 863 | }
|
| 864 |
|
| 865 | static void *c2c_function_he_zalloc(size_t size)
|
| 866 | {
|
| 867 | struct c2c_hist_entry *c2c_he = zalloc(sizeof(*c2c_he) + size);
|
| 868 |
|
| 869 | if (!c2c_he)
|
| 870 | return NULL;
|
| 871 |
|
| 872 | init_stats(&c2c_he->cstats.lcl_hitm);
|
| 873 | init_stats(&c2c_he->cstats.rmt_hitm);
|
| 874 | init_stats(&c2c_he->cstats.lcl_peer);
|
| 875 | init_stats(&c2c_he->cstats.rmt_peer);
|
| 876 | init_stats(&c2c_he->cstats.load);
|
| 877 |
|
| 878 | return &c2c_he->he;
|
| 879 | }
|
| 880 |
|
| 881 |
|
| 882 | static struct hist_entry_ops c2c_function_entry_ops = {
|
| 883 | .new = c2c_function_he_zalloc,
|
| 884 | .free = c2c_function_he_free,
|
| 885 | };
|
| 886 |
|
| 887 | static struct c2c_hist_entry *
|
| 888 | c2c_child_entry__alloc(struct hist_entry *parent_he, struct hist_entry *src_he,
|
| 889 | int depth, u64 ip)
|
| 890 | {
|
| 891 | struct c2c_hist_entry *child_c2c;
|
| 892 | struct hist_entry *child_he;
|
| 893 |
|
| 894 |
|
| 895 | child_he = c2c_function_he_zalloc(0);
|
| 896 | if (!child_he)
|
| 897 | return NULL;
|
| 898 |
|
| 899 | child_c2c = container_of(child_he, struct c2c_hist_entry, he);
|
| 900 | child_he->ops = &c2c_function_entry_ops;
|
| 901 | map_symbol__copy(&child_he->ms, &src_he->ms);
|
| 902 |
|
| 903 | if (src_he->mem_info) {
|
| 904 | child_he->mem_info = mem_info__clone(src_he->mem_info);
|
| 905 | if (!child_he->mem_info)
|
| 906 | goto out_free;
|
| 907 | }
|
| 908 |
|
| 909 | child_he->thread = thread__get(src_he->thread);
|
| 910 | child_he->cpumode = src_he->cpumode;
|
| 911 | child_he->cpu = src_he->cpu;
|
| 912 | child_he->socket = src_he->socket;
|
| 913 | child_he->level = src_he->level;
|
| 914 | child_he->ip = ip;
|
| 915 |
|
| 916 | child_he->parent_he = parent_he;
|
| 917 | child_he->depth = depth;
|
| 918 | child_he->leaf = (depth >= 2);
|
| 919 | child_he->hists = &c2c_ext.function_hists.hists;
|
| 920 | child_he->filtered = false;
|
| 921 | child_he->unfolded = false;
|
| 922 | child_he->has_children = false;
|
| 923 | child_he->has_no_entry = false;
|
| 924 | child_he->nr_rows = 0;
|
| 925 | child_he->row_offset = 0;
|
| 926 |
|
| 927 | memset(&child_he->stat, 0, sizeof(child_he->stat));
|
| 928 | child_he->hroot_in = RB_ROOT_CACHED;
|
| 929 | child_he->hroot_out = RB_ROOT_CACHED;
|
| 930 | INIT_LIST_HEAD(&child_he->pairs.node);
|
| 931 | child_he->hpp_list = &c2c_ext.function_hists.list;
|
| 932 | if (symbol_conf.cumulate_callchain) {
|
| 933 | child_he->stat_acc = calloc(1, sizeof(struct he_stat));
|
| 934 | if (!child_he->stat_acc)
|
| 935 | goto out_free;
|
| 936 | }
|
| 937 |
|
| 938 | return child_c2c;
|
| 939 |
|
| 940 | out_free:
|
| 941 | hist_entry__delete(child_he);
|
| 942 | return NULL;
|
| 943 | }
|
| 944 |
|
| 945 | static void
|
| 946 | c2c_child_entry__insert(struct hist_entry *parent_he, struct hist_entry *child_he,
|
| 947 | struct rb_node **p, struct rb_node *rb_parent, bool leftmost)
|
| 948 | {
|
| 949 | rb_link_node(&child_he->rb_node, rb_parent, p);
|
| 950 | rb_insert_color_cached(&child_he->rb_node, &parent_he->hroot_out, leftmost);
|
| 951 |
|
| 952 | parent_he->has_children = true;
|
| 953 | parent_he->leaf = false;
|
| 954 | }
|
| 955 |
|
| 956 | static struct hist_entry *
|
| 957 | c2c_function_hists__level1_entry(struct symbol *sym,
|
| 958 | struct hist_entry *detail_he,
|
| 959 | struct thread *synthetic_thread)
|
| 960 | {
|
| 961 | struct addr_location al;
|
| 962 | struct perf_sample sample = {};
|
| 963 | struct mem_info *mi;
|
| 964 | struct hist_entry *he;
|
| 965 | |
| 966 | * Key the level-1 entry by the function, not by a specific code
|
| 967 | * address: use the symbol start so every instruction address inside
|
| 968 | * the same function collapses into one entry. This makes level 1 a
|
| 969 | * true "function view" rather than a per-code-address view.
|
| 970 |
|
| 971 | u64 sym_start = (sym && detail_he->ms.map) ?
|
| 972 | map__unmap_ip(detail_he->ms.map, sym->start) : detail_he->ip;
|
| 973 |
|
| 974 | mi = mem_info__new();
|
| 975 | if (!mi)
|
| 976 | return NULL;
|
| 977 |
|
| 978 | mem_info__iaddr(mi)->addr = sym_start;
|
| 979 |
|
| 980 | mem_info__iaddr(mi)->ms.thread = thread__get(detail_he->ms.thread);
|
| 981 | mem_info__iaddr(mi)->ms.map = map__get(detail_he->ms.map);
|
| 982 | mem_info__iaddr(mi)->ms.sym = sym;
|
| 983 | mem_info__daddr(mi)->addr = 0;
|
| 984 |
|
| 985 | addr_location__init(&al);
|
| 986 | al.thread = thread__get(synthetic_thread);
|
| 987 | al.map = map__get(detail_he->ms.map);
|
| 988 | al.sym = sym;
|
| 989 | al.addr = sym_start;
|
| 990 | al.level = detail_he->level;
|
| 991 | al.cpumode = detail_he->cpumode;
|
| 992 | al.cpu = 0;
|
| 993 | al.socket = 0;
|
| 994 | al.filtered = 0;
|
| 995 | al.latency = 0;
|
| 996 |
|
| 997 | |
| 998 | * Synthetic sample: period/weight are placeholders only. The real
|
| 999 | * c2c counters live in c2c_hist_entry::stats and are added via
|
| 1000 | * hist_entry__add_c2c_stats(); no function-view column or sort key
|
| 1001 | * reads he->stat.period/nr_events, so the +1 that __hists__add_entry()
|
| 1002 | * accrues on each dedup hit has no effect on what is displayed.
|
| 1003 |
|
| 1004 | sample.period = 1;
|
| 1005 | sample.weight = 1;
|
| 1006 | sample.ip = sym_start;
|
| 1007 | sample.pid = thread__pid(synthetic_thread);
|
| 1008 | sample.tid = thread__tid(synthetic_thread);
|
| 1009 | sample.cpu = 0;
|
| 1010 |
|
| 1011 |
|
| 1012 | he = hists__add_entry_ops(&c2c_ext.function_hists.hists,
|
| 1013 | &c2c_function_entry_ops,
|
| 1014 | &al, NULL, NULL, mi,
|
| 1015 | NULL, &sample, true);
|
| 1016 |
|
| 1017 | addr_location__exit(&al);
|
| 1018 | mem_info__put(mi);
|
| 1019 |
|
| 1020 | if (he)
|
| 1021 | he->hpp_list = &c2c_ext.function_hists.list;
|
| 1022 |
|
| 1023 | return he;
|
| 1024 | }
|
| 1025 |
|
| 1026 | |
| 1027 | * Level 2: a function that writes a cacheline the level-1 function reads,
|
| 1028 | * keyed by the DSO display name and symbol, consistently with perf's symbol
|
| 1029 | * sort semantics. All code addresses and cachelines for the same writer
|
| 1030 | * function aggregate into one row.
|
| 1031 |
|
| 1032 | static struct c2c_hist_entry *
|
| 1033 | c2c_function_hists__level2_entry(struct c2c_hist_entry *level1_c2c,
|
| 1034 | struct symbol *sym, struct hist_entry *detail_he)
|
| 1035 | {
|
| 1036 | struct hist_entry *level1_he = &level1_c2c->he;
|
| 1037 | struct rb_node **p = &level1_he->hroot_out.rb_root.rb_node;
|
| 1038 | struct rb_node *parent = NULL;
|
| 1039 | struct c2c_hist_entry *level2_c2c;
|
| 1040 | bool leftmost = true;
|
| 1041 |
|
| 1042 | while (*p) {
|
| 1043 | struct hist_entry *iter = rb_entry(*p, struct hist_entry, rb_node);
|
| 1044 | struct map_symbol key = detail_he->ms;
|
| 1045 | int64_t cmp;
|
| 1046 |
|
| 1047 | key.sym = sym;
|
| 1048 | parent = *p;
|
| 1049 | cmp = c2c_function_cmp(&key, &iter->ms);
|
| 1050 |
|
| 1051 | if (cmp < 0) {
|
| 1052 | p = &parent->rb_left;
|
| 1053 | } else if (cmp > 0) {
|
| 1054 | p = &parent->rb_right;
|
| 1055 | leftmost = false;
|
| 1056 | } else {
|
| 1057 | return container_of(iter, struct c2c_hist_entry, he);
|
| 1058 | }
|
| 1059 | }
|
| 1060 |
|
| 1061 |
|
| 1062 | level2_c2c = c2c_child_entry__alloc(level1_he, detail_he, 1,
|
| 1063 | (sym && detail_he->ms.map) ?
|
| 1064 | map__unmap_ip(detail_he->ms.map, sym->start) :
|
| 1065 | hist_entry__iaddr(detail_he));
|
| 1066 | if (!level2_c2c)
|
| 1067 | return NULL;
|
| 1068 |
|
| 1069 |
|
| 1070 | level2_c2c->he.ms.sym = sym;
|
| 1071 | if (level2_c2c->he.mem_info)
|
| 1072 | mem_info__iaddr(level2_c2c->he.mem_info)->ms.sym = sym;
|
| 1073 |
|
| 1074 | c2c_child_entry__insert(level1_he, &level2_c2c->he, p, parent, leftmost);
|
| 1075 |
|
| 1076 | return level2_c2c;
|
| 1077 | }
|
| 1078 |
|
| 1079 |
|
| 1080 | static struct c2c_hist_entry *
|
| 1081 | c2c_function_hists__level3_entry(struct c2c_hist_entry *level2_c2c,
|
| 1082 | struct c2c_hist_entry *cacheline_src_he)
|
| 1083 | {
|
| 1084 | struct hist_entry *level2_he = &level2_c2c->he;
|
| 1085 | struct rb_node **p = &level2_he->hroot_out.rb_root.rb_node;
|
| 1086 | struct rb_node *parent = NULL;
|
| 1087 | struct c2c_hist_entry *level3_c2c;
|
| 1088 | bool leftmost = true;
|
| 1089 |
|
| 1090 | while (*p) {
|
| 1091 | struct c2c_hist_entry *iter_c2c =
|
| 1092 | rb_entry(*p, struct c2c_hist_entry, he.rb_node);
|
| 1093 |
|
| 1094 | parent = *p;
|
| 1095 | if (cacheline_src_he->cacheline_idx < iter_c2c->cacheline_idx) {
|
| 1096 | p = &parent->rb_left;
|
| 1097 | } else if (cacheline_src_he->cacheline_idx > iter_c2c->cacheline_idx) {
|
| 1098 | p = &parent->rb_right;
|
| 1099 | leftmost = false;
|
| 1100 | } else {
|
| 1101 | return iter_c2c;
|
| 1102 | }
|
| 1103 | }
|
| 1104 |
|
| 1105 | level3_c2c = c2c_child_entry__alloc(level2_he, &cacheline_src_he->he, 2,
|
| 1106 | hist_entry__iaddr(&cacheline_src_he->he));
|
| 1107 | if (!level3_c2c)
|
| 1108 | return NULL;
|
| 1109 | level3_c2c->cacheline_idx = cacheline_src_he->cacheline_idx;
|
| 1110 |
|
| 1111 | c2c_child_entry__insert(level2_he, &level3_c2c->he, p, parent, leftmost);
|
| 1112 |
|
| 1113 | return level3_c2c;
|
| 1114 | }
|
| 1115 |
|
| 1116 | struct hist_entry *c2c_function__find_cacheline(struct hist_entry *he_selection)
|
| 1117 | {
|
| 1118 | struct c2c_hist_entry *c2c_he;
|
| 1119 | struct rb_node *nd;
|
| 1120 |
|
| 1121 | if (!c2c_ext.cl_hists || !he_selection || !he_selection->parent_he ||
|
| 1122 | !he_selection->parent_he->parent_he)
|
| 1123 | return NULL;
|
| 1124 |
|
| 1125 | c2c_he = container_of(he_selection, struct c2c_hist_entry, he);
|
| 1126 |
|
| 1127 | for (nd = rb_first_cached(&c2c_ext.cl_hists->hists.entries); nd;
|
| 1128 | nd = rb_next(nd)) {
|
| 1129 | struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
|
| 1130 | struct c2c_hist_entry *cacheline_he;
|
| 1131 |
|
| 1132 | if (he->filtered)
|
| 1133 | continue;
|
| 1134 |
|
| 1135 | cacheline_he = container_of(he, struct c2c_hist_entry, he);
|
| 1136 | if (cacheline_he->hists &&
|
| 1137 | cacheline_he->cacheline_idx == c2c_he->cacheline_idx)
|
| 1138 | return he;
|
| 1139 | }
|
| 1140 |
|
| 1141 | return NULL;
|
| 1142 | }
|
| 1143 |
|
| 1144 | |
| 1145 | * Re-sort child entries of @parent_he by total store count, descending.
|
| 1146 |
|
| 1147 | static void c2c_he__resort_by_stores(struct hist_entry *parent_he)
|
| 1148 | {
|
| 1149 | struct rb_root_cached new_root = RB_ROOT_CACHED;
|
| 1150 | struct rb_node *nd;
|
| 1151 |
|
| 1152 | if (!parent_he->has_children)
|
| 1153 | return;
|
| 1154 |
|
| 1155 |
|
| 1156 | while ((nd = rb_first_cached(&parent_he->hroot_out))) {
|
| 1157 | struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
|
| 1158 | u64 he_store = hist_entry__displayed_stores(he);
|
| 1159 | struct rb_node **p = &new_root.rb_root.rb_node;
|
| 1160 | struct rb_node *parent = NULL;
|
| 1161 | bool leftmost = true;
|
| 1162 | int cmp;
|
| 1163 |
|
| 1164 |
|
| 1165 | rb_erase_cached(&he->rb_node, &parent_he->hroot_out);
|
| 1166 |
|
| 1167 | |
| 1168 | * Insert sorted by store count, descending. Use the displayed
|
| 1169 | * store count so a level-1 function and level-2 writer (whose own
|
| 1170 | * stats.store is 0 / partial) sort by the aggregated write traffic
|
| 1171 | * beneath them, not by their own store field.
|
| 1172 |
|
| 1173 | while (*p) {
|
| 1174 | struct hist_entry *iter = rb_entry(*p, struct hist_entry, rb_node);
|
| 1175 | u64 iter_store = hist_entry__displayed_stores(iter);
|
| 1176 |
|
| 1177 | parent = *p;
|
| 1178 | if (he_store != iter_store) {
|
| 1179 | cmp = he_store > iter_store ? -1 : 1;
|
| 1180 | } else {
|
| 1181 |
|
| 1182 | u64 a = hist_entry__iaddr(he), b = hist_entry__iaddr(iter);
|
| 1183 |
|
| 1184 | if (a != b)
|
| 1185 | cmp = a < b ? -1 : 1;
|
| 1186 | else if (he->ms.sym && iter->ms.sym)
|
| 1187 | cmp = strcmp(he->ms.sym->name,
|
| 1188 | iter->ms.sym->name);
|
| 1189 | else
|
| 1190 | cmp = (iter->ms.sym ? 1 : 0) - (he->ms.sym ? 1 : 0);
|
| 1191 |
|
| 1192 | if (!cmp) {
|
| 1193 | struct c2c_hist_entry *he_c2c;
|
| 1194 | struct c2c_hist_entry *iter_c2c;
|
| 1195 |
|
| 1196 | he_c2c = container_of(he, struct c2c_hist_entry, he);
|
| 1197 | iter_c2c = container_of(iter, struct c2c_hist_entry, he);
|
| 1198 | if (he_c2c->cacheline_idx != iter_c2c->cacheline_idx)
|
| 1199 | cmp = he_c2c->cacheline_idx <
|
| 1200 | iter_c2c->cacheline_idx ? -1 : 1;
|
| 1201 | }
|
| 1202 | }
|
| 1203 |
|
| 1204 | if (cmp < 0) {
|
| 1205 | p = &parent->rb_left;
|
| 1206 | } else {
|
| 1207 | p = &parent->rb_right;
|
| 1208 | leftmost = false;
|
| 1209 | }
|
| 1210 | }
|
| 1211 |
|
| 1212 | rb_link_node(&he->rb_node, parent, p);
|
| 1213 | rb_insert_color_cached(&he->rb_node, &new_root, leftmost);
|
| 1214 | }
|
| 1215 |
|
| 1216 | parent_he->hroot_out = new_root;
|
| 1217 | }
|
| 1218 |
|
| 1219 |
|
| 1220 | #define DEFAULT_SYMBOLS_PER_CL 64
|
| 1221 |
|
| 1222 | struct function_seen {
|
| 1223 | struct map_symbol ms;
|
| 1224 | };
|
| 1225 |
|
| 1226 | static bool function_seen__find(const struct function_seen *seen, int nr,
|
| 1227 | const struct map_symbol *ms)
|
| 1228 | {
|
| 1229 | int i;
|
| 1230 |
|
| 1231 | for (i = 0; i < nr; i++) {
|
| 1232 | if (!c2c_function_cmp(&seen[i].ms, ms))
|
| 1233 | return true;
|
| 1234 | }
|
| 1235 | return false;
|
| 1236 | }
|
| 1237 |
|
| 1238 |
|
| 1239 | static bool c2c_he__add_sharing(struct c2c_hist_entry *dst, struct c2c_hist_entry *src)
|
| 1240 | {
|
| 1241 |
|
| 1242 | if (!hist_entry__add_c2c_stats(&dst->he, &src->stats))
|
| 1243 | return false;
|
| 1244 |
|
| 1245 | c2c_add_stats(&dst->stats, &src->stats);
|
| 1246 | c2c_add_cstats(&dst->cstats, &src->cstats);
|
| 1247 | return true;
|
| 1248 | }
|
| 1249 |
|
| 1250 | |
| 1251 | * Process one cacheline: for every function reading it, create/update its
|
| 1252 | * level-1 function entry, then for each function that writes the line
|
| 1253 | * add it as a level-2 writer and add this cacheline as a level-3 child.
|
| 1254 |
|
| 1255 | static int c2c_function__process_cl(struct c2c_hist_entry *cacheline_he,
|
| 1256 | struct thread *synthetic_thread)
|
| 1257 | {
|
| 1258 | struct rb_node *nd_a, *nd_b;
|
| 1259 | struct function_seen *seen = NULL;
|
| 1260 | int nr_seen = 0, nr_alloc = 0;
|
| 1261 | int ret = 0;
|
| 1262 |
|
| 1263 | for (nd_a = rb_first_cached(&cacheline_he->hists->hists.entries); nd_a;
|
| 1264 | nd_a = rb_next(nd_a)) {
|
| 1265 | struct hist_entry *he_a = rb_entry(nd_a, struct hist_entry, rb_node);
|
| 1266 | struct c2c_hist_entry *c2c_a;
|
| 1267 | struct hist_entry *level1_he;
|
| 1268 | struct c2c_hist_entry *level1_c2c;
|
| 1269 |
|
| 1270 | if (!he_a->ms.sym || he_a->filtered)
|
| 1271 | continue;
|
| 1272 |
|
| 1273 | c2c_a = container_of(he_a, struct c2c_hist_entry, he);
|
| 1274 | if (c2c_a->stats.load == 0)
|
| 1275 | continue;
|
| 1276 |
|
| 1277 | level1_he = c2c_function_hists__level1_entry(he_a->ms.sym,
|
| 1278 | he_a, synthetic_thread);
|
| 1279 | if (!level1_he) {
|
| 1280 | ret = -ENOMEM;
|
| 1281 | goto out;
|
| 1282 | }
|
| 1283 |
|
| 1284 | level1_c2c = container_of(level1_he, struct c2c_hist_entry, he);
|
| 1285 |
|
| 1286 | |
| 1287 | * Aggregate every source entry into its level-1 function parent.
|
| 1288 | * level1_he is keyed by symbol, so all code addresses inside the
|
| 1289 | * same function collapse into one parent. When the cacheline view
|
| 1290 | * splits a function into siblings (different code addresses, or
|
| 1291 | * --coalesce pid/tid/dso), each sibling holds a DISJOINT slice of the
|
| 1292 | * traffic, so summing them here is correct accumulation, not
|
| 1293 | * double counting. The seen[] set below therefore guards only the
|
| 1294 | * subtree build (to avoid building a function's level-2/3 subtree
|
| 1295 | * twice for the same cacheline), never this L1 update. Update
|
| 1296 | * he->stat first; on failure leave the aggregates untouched.
|
| 1297 |
|
| 1298 | if (!hist_entry__add_c2c_stats(level1_he, &c2c_a->stats)) {
|
| 1299 | ret = -ENOMEM;
|
| 1300 | goto out;
|
| 1301 | }
|
| 1302 | c2c_add_stats(&level1_c2c->stats, &c2c_a->stats);
|
| 1303 | c2c_add_cstats(&level1_c2c->cstats, &c2c_a->cstats);
|
| 1304 | c2c_add_stats(&c2c_ext.function_hists.stats, &c2c_a->stats);
|
| 1305 |
|
| 1306 |
|
| 1307 | if (function_seen__find(seen, nr_seen, &he_a->ms))
|
| 1308 | continue;
|
| 1309 |
|
| 1310 | if (nr_seen == nr_alloc) {
|
| 1311 | struct function_seen *tmp;
|
| 1312 | int new_alloc = nr_alloc ? nr_alloc * 2 : DEFAULT_SYMBOLS_PER_CL;
|
| 1313 |
|
| 1314 | tmp = reallocarray(seen, new_alloc, sizeof(*seen));
|
| 1315 | if (!tmp) {
|
| 1316 | ret = -ENOMEM;
|
| 1317 | goto out;
|
| 1318 | }
|
| 1319 | seen = tmp;
|
| 1320 | nr_alloc = new_alloc;
|
| 1321 | }
|
| 1322 | seen[nr_seen].ms = he_a->ms;
|
| 1323 | nr_seen++;
|
| 1324 |
|
| 1325 | for (nd_b = rb_first_cached(&cacheline_he->hists->hists.entries); nd_b;
|
| 1326 | nd_b = rb_next(nd_b)) {
|
| 1327 | struct hist_entry *he_b = rb_entry(nd_b, struct hist_entry, rb_node);
|
| 1328 | struct c2c_hist_entry *c2c_b, *level2_c2c, *level3_c2c;
|
| 1329 |
|
| 1330 | if (!he_b->ms.sym || he_b->filtered)
|
| 1331 | continue;
|
| 1332 |
|
| 1333 | c2c_b = container_of(he_b, struct c2c_hist_entry, he);
|
| 1334 |
|
| 1335 | |
| 1336 | * The level-1 function contributes read-side load weight for this
|
| 1337 | * cacheline. Associate it with functions sampled storing to the
|
| 1338 | * same line.
|
| 1339 | * The writer can be the same function; after detail coalescing and
|
| 1340 | * function-level grouping there is not enough identity to attribute
|
| 1341 | * that case to a specific thread.
|
| 1342 | * Only writers are contending functions, so keep the ones
|
| 1343 | * that actually store into the line.
|
| 1344 |
|
| 1345 | if (c2c_b->stats.store == 0)
|
| 1346 | continue;
|
| 1347 |
|
| 1348 |
|
| 1349 | level2_c2c = c2c_function_hists__level2_entry(level1_c2c, he_b->ms.sym,
|
| 1350 | he_b);
|
| 1351 | if (!level2_c2c || !c2c_he__add_sharing(level2_c2c, c2c_b)) {
|
| 1352 | ret = -ENOMEM;
|
| 1353 | goto out;
|
| 1354 | }
|
| 1355 |
|
| 1356 |
|
| 1357 | level3_c2c = c2c_function_hists__level3_entry(level2_c2c,
|
| 1358 | cacheline_he);
|
| 1359 | if (!level3_c2c || !c2c_he__add_sharing(level3_c2c, c2c_b)) {
|
| 1360 | ret = -ENOMEM;
|
| 1361 | goto out;
|
| 1362 | }
|
| 1363 | }
|
| 1364 | }
|
| 1365 |
|
| 1366 | out:
|
| 1367 | free(seen);
|
| 1368 | return ret;
|
| 1369 | }
|
| 1370 |
|
| 1371 | |
| 1372 | * Remove a level-1 function that has no contended cachelines left. It is a
|
| 1373 | * normal (owned) hist_entry in function_hists, so mirror hists__delete_entry()
|
| 1374 | * for the no-collapse case: unlink from both trees, fix the counters, then
|
| 1375 | * delete. Its hroot_out is already empty after pruning.
|
| 1376 |
|
| 1377 | static void c2c_function__drop_level1(struct hist_entry *he)
|
| 1378 | {
|
| 1379 | struct hists *hists = &c2c_ext.function_hists.hists;
|
| 1380 |
|
| 1381 | rb_erase_cached(&he->rb_node_in, hists->entries_in);
|
| 1382 | rb_erase_cached(&he->rb_node, &hists->entries);
|
| 1383 |
|
| 1384 | --hists->nr_entries;
|
| 1385 | if (!he->filtered)
|
| 1386 | --hists->nr_non_filtered_entries;
|
| 1387 |
|
| 1388 | hist_entry__delete(he);
|
| 1389 | }
|
| 1390 |
|
| 1391 |
|
| 1392 | static int c2c_function__ident_len(struct hist_entry *he)
|
| 1393 | {
|
| 1394 | char buf[512];
|
| 1395 | char *symbuf;
|
| 1396 | size_t size;
|
| 1397 | int len;
|
| 1398 |
|
| 1399 | if (hist_entry__is_cacheline(he)) {
|
| 1400 | u64 addr = he->mem_info ?
|
| 1401 | cl_address(mem_info__daddr(he->mem_info)->addr, chk_double_cl) : 0;
|
| 1402 |
|
| 1403 | return scnprintf(buf, sizeof(buf), "0x%" PRIx64, addr);
|
| 1404 | }
|
| 1405 |
|
| 1406 | if (!he->ms.sym)
|
| 1407 | return 0;
|
| 1408 |
|
| 1409 | |
| 1410 | * Match symbol_view_entry(): sort_sym adds the cpumode prefix and, in
|
| 1411 | * verbose mode, the address and DSO origin before the symbol name.
|
| 1412 |
|
| 1413 | size = strlen(he->ms.sym->name) + 64;
|
| 1414 | symbuf = malloc(size);
|
| 1415 | if (!symbuf)
|
| 1416 | return size - 1;
|
| 1417 |
|
| 1418 | len = sort_sym.se_snprintf(he, symbuf, size, size - 1);
|
| 1419 | free(symbuf);
|
| 1420 | return len;
|
| 1421 | }
|
| 1422 |
|
| 1423 | |
| 1424 | * Grow the symbol column so the deepest, longest identity cell fits. The
|
| 1425 | * generic hists__calc_col_len() only measures the top-level (L1) entries; the
|
| 1426 | * hand-linked L2 writers and L3 cacheline addresses live in hroot_out and are
|
| 1427 | * never measured, so with a short L1 name the indented L2/L3 text would be
|
| 1428 | * truncated. Account for the per-level indent and the folded-sign prefix.
|
| 1429 |
|
| 1430 | static void c2c_function__update_symbol_width(struct hist_entry *he)
|
| 1431 | {
|
| 1432 | struct hists *hists = &c2c_ext.function_hists.hists;
|
| 1433 | int need = he->depth * C2C_FUNC_INDENT + C2C_FUNC_FOLD_WIDTH +
|
| 1434 | c2c_function__ident_len(he);
|
| 1435 |
|
| 1436 | if (need > hists__col_len(hists, HISTC_SYMBOL))
|
| 1437 | hists__set_col_len(hists, HISTC_SYMBOL, need);
|
| 1438 | }
|
| 1439 |
|
| 1440 | |
| 1441 | * Prune writers with no stores, drop functions left with no contending
|
| 1442 | * writer, sort the survivors by store count, then compute the global total.
|
| 1443 |
|
| 1444 | static void c2c_function__finalize(void)
|
| 1445 | {
|
| 1446 | struct rb_node *nd_l1;
|
| 1447 |
|
| 1448 | nd_l1 = rb_first_cached(&c2c_ext.function_hists.hists.entries);
|
| 1449 | while (nd_l1) {
|
| 1450 | struct hist_entry *he_l1 = rb_entry(nd_l1, struct hist_entry, rb_node);
|
| 1451 | struct rb_node *next_l1 = rb_next(nd_l1);
|
| 1452 | struct rb_node *nd_l2;
|
| 1453 |
|
| 1454 |
|
| 1455 | if (!he_l1->has_children || !c2c_he__prune_empty_writers(he_l1)) {
|
| 1456 |
|
| 1457 | c2c_function__drop_level1(he_l1);
|
| 1458 | nd_l1 = next_l1;
|
| 1459 | continue;
|
| 1460 | }
|
| 1461 |
|
| 1462 | c2c_he__resort_by_stores(he_l1);
|
| 1463 | c2c_function__update_symbol_width(he_l1);
|
| 1464 |
|
| 1465 | for (nd_l2 = rb_first_cached(&he_l1->hroot_out); nd_l2;
|
| 1466 | nd_l2 = rb_next(nd_l2)) {
|
| 1467 | struct hist_entry *he_l2 = rb_entry(nd_l2, struct hist_entry, rb_node);
|
| 1468 | struct rb_node *nd_l3;
|
| 1469 |
|
| 1470 | c2c_function__update_symbol_width(he_l2);
|
| 1471 |
|
| 1472 | if (he_l2->has_children)
|
| 1473 | c2c_he__resort_by_stores(he_l2);
|
| 1474 |
|
| 1475 | for (nd_l3 = rb_first_cached(&he_l2->hroot_out); nd_l3;
|
| 1476 | nd_l3 = rb_next(nd_l3)) {
|
| 1477 | struct hist_entry *he_l3 = rb_entry(nd_l3, struct hist_entry,
|
| 1478 | rb_node);
|
| 1479 |
|
| 1480 | c2c_function__update_symbol_width(he_l3);
|
| 1481 | }
|
| 1482 | }
|
| 1483 |
|
| 1484 | nd_l1 = next_l1;
|
| 1485 | }
|
| 1486 |
|
| 1487 | |
| 1488 | * Compute the Cycles % denominator from the surviving level-1 entries
|
| 1489 | * after pruning, so the column shows each function's share of the
|
| 1490 | * functions retained in this table -- not of the whole recording. See
|
| 1491 | * the Cycles % description in perf-c2c.txt.
|
| 1492 |
|
| 1493 | c2c_ext.total_cycles = c2c_ext__total_cycles();
|
| 1494 | }
|
| 1495 |
|
| 1496 | |
| 1497 | * Release all per-visit function-view state. Keep the hists object itself
|
| 1498 | * initialized so its mutex is initialized exactly once across TAB re-entry.
|
| 1499 |
|
| 1500 | void c2c_function__reset(void)
|
| 1501 | {
|
| 1502 | bool saved_use_callchain = symbol_conf.use_callchain;
|
| 1503 |
|
| 1504 | |
| 1505 | * Function-view entries never carry callchains. Keep their generic
|
| 1506 | * destructor independent of the caller's current callchain setting.
|
| 1507 |
|
| 1508 | symbol_conf.use_callchain = false;
|
| 1509 | hists__delete_all_entries(&c2c_ext.function_hists.hists);
|
| 1510 | if (c2c_ext.function_hists.list.fields.next)
|
| 1511 | perf_hpp__reset_output_field(&c2c_ext.function_hists.list);
|
| 1512 |
|
| 1513 | memset(&c2c_ext.function_hists.stats, 0,
|
| 1514 | sizeof(c2c_ext.function_hists.stats));
|
| 1515 | c2c_ext.total_cycles = 0;
|
| 1516 | c2c_ext.cl_hists = NULL;
|
| 1517 | c2c_ext.cl_sort = NULL;
|
| 1518 | c2c_ext.symbol_full = false;
|
| 1519 | symbol_conf.use_callchain = saved_use_callchain;
|
| 1520 | }
|
| 1521 |
|
| 1522 | static bool c2c_function__has_iaddr(const char *cl_sort)
|
| 1523 | {
|
| 1524 | const char *field = cl_sort;
|
| 1525 |
|
| 1526 | while (field && *field) {
|
| 1527 | const char *end = strchr(field, ',');
|
| 1528 | size_t len = end ? (size_t)(end - field) : strlen(field);
|
| 1529 |
|
| 1530 | if (len == sizeof("iaddr") - 1 && !strncmp(field, "iaddr", len))
|
| 1531 | return true;
|
| 1532 | field = end ? end + 1 : NULL;
|
| 1533 | }
|
| 1534 | return false;
|
| 1535 | }
|
| 1536 |
|
| 1537 | |
| 1538 | * Build the three-level function view in a single pass over the cacheline
|
| 1539 | * entries:
|
| 1540 | * L1: read-side functions (aggregated across all their code addresses)
|
| 1541 | * L2: writing functions contending with each level-1 function
|
| 1542 | * L3: shared cachelines for each function pair
|
| 1543 |
|
| 1544 | int c2c_function__build(struct c2c_hists *cl_hists, const char *cl_sort,
|
| 1545 | bool symbol_full, struct hists **hists)
|
| 1546 | {
|
| 1547 | static const char output_fields[] =
|
| 1548 | "cycles_percent,total_stores,symbol_view";
|
| 1549 | static bool hists_initialized;
|
| 1550 | struct rb_node *nd_cl;
|
| 1551 | bool saved_use_callchain;
|
| 1552 | int ret;
|
| 1553 |
|
| 1554 | if (!hists)
|
| 1555 | return -EINVAL;
|
| 1556 | *hists = NULL;
|
| 1557 |
|
| 1558 | if (!cl_hists || !cl_sort)
|
| 1559 | return -EINVAL;
|
| 1560 | if (!c2c_function__has_iaddr(cl_sort))
|
| 1561 | return -EOPNOTSUPP;
|
| 1562 |
|
| 1563 | saved_use_callchain = symbol_conf.use_callchain;
|
| 1564 | symbol_conf.use_callchain = false;
|
| 1565 | c2c_function__reset();
|
| 1566 |
|
| 1567 | c2c_ext.cl_hists = cl_hists;
|
| 1568 | c2c_ext.cl_sort = cl_sort;
|
| 1569 | c2c_ext.symbol_full = symbol_full;
|
| 1570 |
|
| 1571 | |
| 1572 | * __hists__init() (called by c2c_function_hists__init()) also
|
| 1573 | * mutex_init()s the hists lock, so only run it once for this static
|
| 1574 | * hists; on re-entry just re-parse the columns via reinit().
|
| 1575 |
|
| 1576 | if (!hists_initialized) {
|
| 1577 | ret = c2c_function_hists__init(&c2c_ext.function_hists,
|
| 1578 | "symbol_view", 2, NULL);
|
| 1579 | hists_initialized = true;
|
| 1580 | } else {
|
| 1581 | ret = c2c_function_hists__reinit(&c2c_ext.function_hists,
|
| 1582 | NULL, "symbol_view", NULL);
|
| 1583 | }
|
| 1584 | if (ret)
|
| 1585 | goto out_reset;
|
| 1586 |
|
| 1587 | nd_cl = rb_first_cached(&c2c_ext.cl_hists->hists.entries);
|
| 1588 |
|
| 1589 |
|
| 1590 | for (; nd_cl; nd_cl = rb_next(nd_cl)) {
|
| 1591 | struct hist_entry *he_cl = rb_entry(nd_cl, struct hist_entry, rb_node);
|
| 1592 | struct c2c_hist_entry *cacheline_he = container_of(he_cl,
|
| 1593 | struct c2c_hist_entry, he);
|
| 1594 | struct thread *synthetic_thread = he_cl->thread;
|
| 1595 |
|
| 1596 | |
| 1597 | * Detail hists are finalized only for cachelines retained by the
|
| 1598 | * top-level C2C filter. Among those, include any line with sharing
|
| 1599 | * activity, not just HITM.
|
| 1600 |
|
| 1601 | if (he_cl->filtered ||
|
| 1602 | (c2c_hitm_count(&cacheline_he->stats) == 0 &&
|
| 1603 | cacheline_he->stats.tot_peer == 0 &&
|
| 1604 | cacheline_he->stats.store == 0 &&
|
| 1605 | cacheline_he->stats.load == 0) ||
|
| 1606 | !cacheline_he->hists ||
|
| 1607 | RB_EMPTY_ROOT(&cacheline_he->hists->hists.entries.rb_root) ||
|
| 1608 | !he_cl->mem_info || !synthetic_thread)
|
| 1609 | continue;
|
| 1610 |
|
| 1611 | ret = c2c_function__process_cl(cacheline_he, synthetic_thread);
|
| 1612 | if (ret)
|
| 1613 | goto out_err;
|
| 1614 | }
|
| 1615 |
|
| 1616 | ret = c2c_function_hists__reinit(&c2c_ext.function_hists, output_fields,
|
| 1617 | "cycles_percent", NULL);
|
| 1618 | if (ret)
|
| 1619 | goto out_err;
|
| 1620 |
|
| 1621 | hists__collapse_resort(&c2c_ext.function_hists.hists, NULL);
|
| 1622 | hists__output_resort(&c2c_ext.function_hists.hists, NULL);
|
| 1623 |
|
| 1624 | c2c_function__finalize();
|
| 1625 |
|
| 1626 | *hists = &c2c_ext.function_hists.hists;
|
| 1627 | symbol_conf.use_callchain = saved_use_callchain;
|
| 1628 | return 0;
|
| 1629 |
|
| 1630 | out_err:
|
| 1631 | |
| 1632 | * On error, migrate any entries still in entries_in to entries and
|
| 1633 | * delete them, so a later rebuild does not strand them (the top-level
|
| 1634 | * __hists__init() memset would otherwise lose the pointers).
|
| 1635 |
|
| 1636 | hists__collapse_resort(&c2c_ext.function_hists.hists, NULL);
|
| 1637 | hists__output_resort(&c2c_ext.function_hists.hists, NULL);
|
| 1638 | out_reset:
|
| 1639 | c2c_function__reset();
|
| 1640 | symbol_conf.use_callchain = saved_use_callchain;
|
| 1641 | return ret;
|
| 1642 | }
|