1 // SPDX-License-Identifier: GPL-2.0 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> /* reallocarray */ 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 /* Total estimated cycles across all level-1 entries. */ 39 u64 total_cycles; 40 /* Source cacheline histograms; not owned here. */ 41 struct c2c_hists *cl_hists; 42 /* --coalesce field list, used to require iaddr. */ 43 const char *cl_sort; 44 /* Do not cap long symbol names. */ 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; /* level 3: cacheline */ 99 } 100 101 /* Spaces of indent per hierarchy level, like the normal report view. */ 102 #define C2C_FUNC_INDENT 2 103 104 /* Width of the folded-sign prefix ("%c ") each identity cell emits. */ 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 /* Use the last line from sort_entry if not defined. */ 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 /* Sum c2c_hist_entry__cycles() across all level-1 entries. */ 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 /* Level-2/3 entries already aggregate the stores they represent. */ 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 /* Level 3: the shared cacheline address. */ 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 /* Level 1 and level 2 are both functions. */ 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 /* Hide Cycles Percent for child functions and cachelines. */ 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 /* Populated by c2c_function__build() once the L1 tree is built. */ 363 total_cycles = c2c_ext.total_cycles; 364 pct = total_cycles > 0 ? (double)fn_cycles / total_cycles * 100.0 : 0.0; 365 366 /* Add folded sign only for level-1 entries */ 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 /* Cycles Percent is only shown for level-1 entries; others compare equal. */ 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 /* Wrappers so sort_entry-backed dimensions sort/collapse via their se. */ 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 /* The function view only accepts its own dimensions. */ 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 /* The function view only accepts its own dimensions. */ 573 if (ret == -ENOENT) 574 return -EINVAL; 575 576 /* Mark symbol-backed sort keys so hists__has(hists, sym) is correct. */ 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, /*output=*/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 /* Clear stale state flags so a different output/sort set starts fresh. */ 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 /* Welford online merge of two "stats" (from util/stat.h) accumulators. */ 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 /* Update min/max */ 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 /* Merge compute_stats during function aggregation. */ 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 /* All children erased; clear the tree (and its cached leftmost). */ 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 /* Entry operations for function view */ 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 /* Function-view children never own or display callchains. */ 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 /* mem_info__put() will map_symbol__exit() these, so take refs. */ 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 /* Add entry - histogram handles dedup */ 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 /* Key by the function symbol start so all code addresses collapse. */ 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 /* Key this level by the looked-up symbol, not detail_he's. */ 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 /* Level 3: one source cacheline where the L1/L2 functions contend. */ 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 /* Extract all nodes and re-insert sorted by displayed store count */ 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 /* Remove from current tree */ 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 /* Stable tie-break: instruction address, name, then cacheline. */ 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 /* Initial per-cacheline capacity for the seen[] set; grown on demand. */ 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 /* Aggregate stats from the cacheline-side entry @c2c_b into level 2/3 @dst. */ 1239 static bool c2c_he__add_sharing(struct c2c_hist_entry *dst, struct c2c_hist_entry *src) 1240 { 1241 /* Do the fallible update first so a failure leaves dst unmodified. */ 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 /* Build this function's subtree for this cacheline only once. */ 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 /* Level 2: the writing function (aggregated across cachelines). */ 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 /* Level 3: the specific cacheline they contend over. */ 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 /* Length of the identity text (symbol name or cacheline address) at @he. */ 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 /* Drop writers with no stores before sorting. */ 1455 if (!he_l1->has_children || !c2c_he__prune_empty_writers(he_l1)) { 1456 /* No contending writer: this function is not shared. */ 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 /*output=*/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 /* An empty C2C report yields an empty (but valid) function view. */ 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 } 1643