614 lines · c
1// SPDX-License-Identifier: GPL-2.02#include <inttypes.h>3#include <string.h>4#include <linux/zalloc.h>5#include <sys/ttydefaults.h>6 7#include "ui/browser.h"8#include "ui/helpline.h"9#include "ui/keysyms.h"10#include "ui/ui.h"11#include "util/annotate.h"12#include "util/annotate-data.h"13#include "util/evsel.h"14#include "util/evlist.h"15#include "util/sort.h"16 17#define FOLDED_SIGN '+'18#define UNFOLD_SIGN '-'19#define NOCHLD_SIGN ' '20 21struct browser_entry {22 struct list_head node;23 struct annotated_member *data;24 struct type_hist_entry *hists;25 struct browser_entry *parent;26 struct list_head children;27 int indent; /*indentation level, starts from 0 */28 int nr_entries; /* # of visible entries: self + descendents */29 bool folded; /* only can be false when it has children */30};31 32struct annotated_data_browser {33 struct ui_browser b;34 struct list_head entries;35 struct browser_entry *curr;36 int nr_events;37};38 39static struct annotated_data_browser *get_browser(struct ui_browser *uib)40{41 return container_of(uib, struct annotated_data_browser, b);42}43 44static void update_hist_entry(struct type_hist_entry *dst,45 struct type_hist_entry *src)46{47 dst->nr_samples += src->nr_samples;48 dst->period += src->period;49}50 51static int get_member_overhead(struct annotated_data_type *adt,52 struct browser_entry *entry,53 struct evsel *leader)54{55 struct annotated_member *member = entry->data;56 int i, k;57 58 for (i = 0; i < member->size; i++) {59 struct type_hist *h;60 struct evsel *evsel;61 int offset = member->offset + i;62 63 k = 0;64 for_each_group_evsel(evsel, leader) {65 if (symbol_conf.skip_empty &&66 evsel__hists(evsel)->stats.nr_samples == 0)67 continue;68 69 h = adt->histograms[evsel->core.idx];70 update_hist_entry(&entry->hists[k++], &h->addr[offset]);71 }72 }73 return 0;74}75 76static int add_child_entries(struct annotated_data_browser *browser,77 struct browser_entry *parent,78 struct annotated_data_type *adt,79 struct annotated_member *member,80 struct evsel *evsel, int indent)81{82 struct annotated_member *pos;83 struct browser_entry *entry;84 struct list_head *parent_list;85 86 entry = zalloc(sizeof(*entry));87 if (entry == NULL)88 return -1;89 90 entry->hists = calloc(browser->nr_events, sizeof(*entry->hists));91 if (entry->hists == NULL) {92 free(entry);93 return -1;94 }95 96 entry->data = member;97 entry->parent = parent;98 entry->indent = indent;99 if (get_member_overhead(adt, entry, evsel) < 0) {100 free(entry);101 return -1;102 }103 104 INIT_LIST_HEAD(&entry->children);105 if (parent)106 parent_list = &parent->children;107 else108 parent_list = &browser->entries;109 110 list_add_tail(&entry->node, parent_list);111 112 list_for_each_entry(pos, &member->children, node) {113 int nr = add_child_entries(browser, entry, adt, pos, evsel,114 indent + 1);115 if (nr < 0)116 return nr;117 }118 119 /* add an entry for the closing bracket ("}") */120 if (!list_empty(&member->children)) {121 struct browser_entry *bracket;122 123 bracket = zalloc(sizeof(*bracket));124 if (bracket == NULL)125 return -1;126 127 bracket->indent = indent;128 bracket->parent = entry;129 bracket->folded = true;130 bracket->nr_entries = 1;131 132 INIT_LIST_HEAD(&bracket->children);133 list_add_tail(&bracket->node, &entry->children);134 }135 136 /* fold child entries by default */137 entry->folded = true;138 entry->nr_entries = 1;139 return 0;140}141 142static u32 count_visible_entries(struct annotated_data_browser *browser)143{144 int nr = 0;145 struct browser_entry *entry;146 147 list_for_each_entry(entry, &browser->entries, node)148 nr += entry->nr_entries;149 150 return nr;151}152 153static int annotated_data_browser__collect_entries(struct annotated_data_browser *browser)154{155 struct hist_entry *he = browser->b.priv;156 struct annotated_data_type *adt = he->mem_type;157 struct evsel *evsel = hists_to_evsel(he->hists);158 159 INIT_LIST_HEAD(&browser->entries);160 161 add_child_entries(browser, /*parent=*/NULL, adt, &adt->self, evsel,162 /*indent=*/0);163 164 browser->b.entries = &browser->entries;165 browser->b.nr_entries = count_visible_entries(browser);166 return 0;167}168 169static void annotated_data_browser__delete_entries(struct annotated_data_browser *browser)170{171 struct browser_entry *pos, *tmp;172 173 list_for_each_entry_safe(pos, tmp, &browser->entries, node) {174 list_del_init(&pos->node);175 zfree(&pos->hists);176 free(pos);177 }178}179 180static struct browser_entry *get_first_child(struct browser_entry *entry)181{182 if (list_empty(&entry->children))183 return NULL;184 185 return list_first_entry(&entry->children, struct browser_entry, node);186}187 188static struct browser_entry *get_last_child(struct browser_entry *entry)189{190 if (list_empty(&entry->children))191 return NULL;192 193 return list_last_entry(&entry->children, struct browser_entry, node);194}195 196static bool is_first_child(struct browser_entry *entry)197{198 /* This will be checked in a different way */199 if (entry->parent == NULL)200 return false;201 202 return get_first_child(entry->parent) == entry;203}204 205static bool is_last_child(struct browser_entry *entry)206{207 /* This will be checked in a different way */208 if (entry->parent == NULL)209 return false;210 211 return get_last_child(entry->parent) == entry;212}213 214static struct browser_entry *browser__prev_entry(struct ui_browser *uib,215 struct browser_entry *entry)216{217 struct annotated_data_browser *browser = get_browser(uib);218 struct browser_entry *first;219 220 first = list_first_entry(&browser->entries, struct browser_entry, node);221 222 while (entry != first) {223 if (is_first_child(entry))224 entry = entry->parent;225 else {226 entry = list_prev_entry(entry, node);227 while (!entry->folded)228 entry = get_last_child(entry);229 }230 231 if (!uib->filter || !uib->filter(uib, &entry->node))232 return entry;233 }234 return first;235}236 237static struct browser_entry *browser__next_entry(struct ui_browser *uib,238 struct browser_entry *entry)239{240 struct annotated_data_browser *browser = get_browser(uib);241 struct browser_entry *last;242 243 last = list_last_entry(&browser->entries, struct browser_entry, node);244 while (!last->folded)245 last = get_last_child(last);246 247 while (entry != last) {248 if (!entry->folded)249 entry = get_first_child(entry);250 else {251 while (is_last_child(entry))252 entry = entry->parent;253 254 entry = list_next_entry(entry, node);255 }256 257 if (!uib->filter || !uib->filter(uib, &entry->node))258 return entry;259 }260 return last;261}262 263static void browser__seek(struct ui_browser *uib, off_t offset, int whence)264{265 struct annotated_data_browser *browser = get_browser(uib);266 struct browser_entry *entry;267 268 if (uib->nr_entries == 0)269 return;270 271 switch (whence) {272 case SEEK_SET:273 entry = list_first_entry(&browser->entries, typeof(*entry), node);274 if (uib->filter && uib->filter(uib, &entry->node))275 entry = browser__next_entry(uib, entry);276 break;277 case SEEK_CUR:278 entry = list_entry(uib->top, typeof(*entry), node);279 break;280 case SEEK_END:281 entry = list_last_entry(&browser->entries, typeof(*entry), node);282 while (!entry->folded)283 entry = get_last_child(entry);284 if (uib->filter && uib->filter(uib, &entry->node))285 entry = browser__prev_entry(uib, entry);286 break;287 default:288 return;289 }290 291 assert(entry != NULL);292 293 if (offset > 0) {294 while (offset-- != 0)295 entry = browser__next_entry(uib, entry);296 } else {297 while (offset++ != 0)298 entry = browser__prev_entry(uib, entry);299 }300 301 uib->top = &entry->node;302}303 304static unsigned int browser__refresh(struct ui_browser *uib)305{306 struct annotated_data_browser *browser = get_browser(uib);307 struct browser_entry *entry, *next;308 int row = 0;309 310 if (uib->top == NULL || uib->top == uib->entries)311 browser__seek(uib, SEEK_SET, 0);312 313 entry = list_entry(uib->top, typeof(*entry), node);314 315 while (true) {316 if (!uib->filter || !uib->filter(uib, &entry->node)) {317 ui_browser__gotorc(uib, row, 0);318 uib->write(uib, entry, row);319 if (uib->top_idx + row == uib->index)320 browser->curr = entry;321 if (++row == uib->rows)322 break;323 }324 next = browser__next_entry(uib, entry);325 if (next == entry)326 break;327 328 entry = next;329 }330 331 return row;332}333 334static int browser__show(struct ui_browser *uib)335{336 struct hist_entry *he = uib->priv;337 struct annotated_data_type *adt = he->mem_type;338 struct annotated_data_browser *browser = get_browser(uib);339 const char *help = "Press 'h' for help on key bindings";340 char title[256];341 342 snprintf(title, sizeof(title), "Annotate type: '%s' (%d samples)",343 adt->self.type_name, he->stat.nr_events);344 345 if (ui_browser__show(uib, title, help) < 0)346 return -1;347 348 /* second line header */349 ui_browser__gotorc_title(uib, 0, 0);350 ui_browser__set_color(uib, HE_COLORSET_ROOT);351 352 if (symbol_conf.show_total_period)353 strcpy(title, "Period");354 else if (symbol_conf.show_nr_samples)355 strcpy(title, "Samples");356 else357 strcpy(title, "Percent");358 359 ui_browser__printf(uib, "%*s %10s %10s %10s %s",360 2 + 11 * (browser->nr_events - 1), "",361 title, "Offset", "Size", "Field");362 ui_browser__write_nstring(uib, "", uib->width);363 return 0;364}365 366static void browser__write_overhead(struct ui_browser *uib,367 struct type_hist *total,368 struct type_hist_entry *hist, int row)369{370 u64 period = hist->period;371 double percent = total->period ? (100.0 * period / total->period) : 0;372 bool current = ui_browser__is_current_entry(uib, row);373 int nr_samples = 0;374 375 ui_browser__set_percent_color(uib, percent, current);376 377 if (symbol_conf.show_total_period)378 ui_browser__printf(uib, " %10" PRIu64, period);379 else if (symbol_conf.show_nr_samples)380 ui_browser__printf(uib, " %10d", nr_samples);381 else382 ui_browser__printf(uib, " %10.2f", percent);383 384 ui_browser__set_percent_color(uib, 0, current);385}386 387static void browser__write(struct ui_browser *uib, void *entry, int row)388{389 struct annotated_data_browser *browser = get_browser(uib);390 struct browser_entry *be = entry;391 struct annotated_member *member = be->data;392 struct hist_entry *he = uib->priv;393 struct annotated_data_type *adt = he->mem_type;394 struct evsel *leader = hists_to_evsel(he->hists);395 struct evsel *evsel;396 int idx = 0;397 bool current = ui_browser__is_current_entry(uib, row);398 399 if (member == NULL) {400 /* print the closing bracket */401 ui_browser__set_percent_color(uib, 0, current);402 ui_browser__printf(uib, "%c ", NOCHLD_SIGN);403 ui_browser__write_nstring(uib, "", 11 * browser->nr_events);404 ui_browser__printf(uib, " %10s %10s %*s};",405 "", "", be->indent * 4, "");406 ui_browser__write_nstring(uib, "", uib->width);407 return;408 }409 410 ui_browser__set_percent_color(uib, 0, current);411 412 if (!list_empty(&be->children))413 ui_browser__printf(uib, "%c ", be->folded ? FOLDED_SIGN : UNFOLD_SIGN);414 else415 ui_browser__printf(uib, "%c ", NOCHLD_SIGN);416 417 /* print the number */418 for_each_group_evsel(evsel, leader) {419 struct type_hist *h = adt->histograms[evsel->core.idx];420 421 if (symbol_conf.skip_empty &&422 evsel__hists(evsel)->stats.nr_samples == 0)423 continue;424 425 browser__write_overhead(uib, h, &be->hists[idx++], row);426 }427 428 /* print type info */429 if (be->indent == 0 && !member->var_name) {430 ui_browser__printf(uib, " %#10x %#10x %s%s",431 member->offset, member->size,432 member->type_name,433 list_empty(&member->children) || be->folded? ";" : " {");434 } else {435 ui_browser__printf(uib, " %#10x %#10x %*s%s\t%s%s",436 member->offset, member->size,437 be->indent * 4, "", member->type_name,438 member->var_name ?: "",439 list_empty(&member->children) || be->folded ? ";" : " {");440 }441 /* fill the rest */442 ui_browser__write_nstring(uib, "", uib->width);443}444 445static void annotated_data_browser__fold(struct annotated_data_browser *browser,446 struct browser_entry *entry,447 bool recursive)448{449 struct browser_entry *child;450 451 if (list_empty(&entry->children))452 return;453 if (entry->folded && !recursive)454 return;455 456 if (recursive) {457 list_for_each_entry(child, &entry->children, node)458 annotated_data_browser__fold(browser, child, true);459 }460 461 entry->nr_entries = 1;462 entry->folded = true;463}464 465static void annotated_data_browser__unfold(struct annotated_data_browser *browser,466 struct browser_entry *entry,467 bool recursive)468{469 struct browser_entry *child;470 int nr_entries;471 472 if (list_empty(&entry->children))473 return;474 if (!entry->folded && !recursive)475 return;476 477 nr_entries = 1; /* for self */478 list_for_each_entry(child, &entry->children, node) {479 if (recursive)480 annotated_data_browser__unfold(browser, child, true);481 482 nr_entries += child->nr_entries;483 }484 485 entry->nr_entries = nr_entries;486 entry->folded = false;487}488 489static void annotated_data_browser__toggle_fold(struct annotated_data_browser *browser,490 bool recursive)491{492 struct browser_entry *curr = browser->curr;493 struct browser_entry *parent;494 495 parent = curr->parent;496 while (parent) {497 parent->nr_entries -= curr->nr_entries;498 parent = parent->parent;499 }500 browser->b.nr_entries -= curr->nr_entries;501 502 if (curr->folded)503 annotated_data_browser__unfold(browser, curr, recursive);504 else505 annotated_data_browser__fold(browser, curr, recursive);506 507 parent = curr->parent;508 while (parent) {509 parent->nr_entries += curr->nr_entries;510 parent = parent->parent;511 }512 browser->b.nr_entries += curr->nr_entries;513 514 assert(browser->b.nr_entries == count_visible_entries(browser));515}516 517static int annotated_data_browser__run(struct annotated_data_browser *browser,518 struct evsel *evsel __maybe_unused,519 struct hist_browser_timer *hbt)520{521 int delay_secs = hbt ? hbt->refresh : 0;522 int key;523 524 if (browser__show(&browser->b) < 0)525 return -1;526 527 while (1) {528 key = ui_browser__run(&browser->b, delay_secs);529 530 switch (key) {531 case K_TIMER:532 if (hbt)533 hbt->timer(hbt->arg);534 continue;535 case K_F1:536 case 'h':537 ui_browser__help_window(&browser->b,538 "UP/DOWN/PGUP\n"539 "PGDN/SPACE Navigate\n"540 "</> Move to prev/next symbol\n"541 "e Expand/Collapse current entry\n"542 "E Expand/Collapse all children of the current\n"543 "q/ESC/CTRL+C Exit\n\n");544 continue;545 case 'e':546 annotated_data_browser__toggle_fold(browser,547 /*recursive=*/false);548 break;549 case 'E':550 annotated_data_browser__toggle_fold(browser,551 /*recursive=*/true);552 break;553 case K_LEFT:554 case '<':555 case '>':556 case K_ESC:557 case 'q':558 case CTRL('c'):559 goto out;560 default:561 continue;562 }563 }564out:565 ui_browser__hide(&browser->b);566 return key;567}568 569int hist_entry__annotate_data_tui(struct hist_entry *he, struct evsel *evsel,570 struct hist_browser_timer *hbt)571{572 struct annotated_data_browser browser = {573 .b = {574 .refresh = browser__refresh,575 .seek = browser__seek,576 .write = browser__write,577 .priv = he,578 .extra_title_lines = 1,579 },580 .nr_events = 1,581 };582 int ret;583 584 ui_helpline__push("Press ESC to exit");585 586 if (evsel__is_group_event(evsel)) {587 struct evsel *pos;588 int nr = 0;589 590 for_each_group_evsel(pos, evsel) {591 if (!symbol_conf.skip_empty ||592 evsel__hists(pos)->stats.nr_samples)593 nr++;594 }595 browser.nr_events = nr;596 }597 598 ret = annotated_data_browser__collect_entries(&browser);599 if (ret < 0)600 goto out;601 602 /* To get the top and current entry */603 browser__refresh(&browser.b);604 /* Show the first-level child entries by default */605 annotated_data_browser__toggle_fold(&browser, /*recursive=*/false);606 607 ret = annotated_data_browser__run(&browser, evsel, hbt);608 609out:610 annotated_data_browser__delete_entries(&browser);611 612 return ret;613}614