linux/tools/perf/util/ui/browsers/hists.c
<<
>>
Prefs
   1#define _GNU_SOURCE
   2#include <stdio.h>
   3#undef _GNU_SOURCE
   4#include "../libslang.h"
   5#include <stdlib.h>
   6#include <string.h>
   7#include <newt.h>
   8#include <linux/rbtree.h>
   9
  10#include "../../evsel.h"
  11#include "../../evlist.h"
  12#include "../../hist.h"
  13#include "../../pstack.h"
  14#include "../../sort.h"
  15#include "../../util.h"
  16
  17#include "../browser.h"
  18#include "../helpline.h"
  19#include "../util.h"
  20#include "map.h"
  21
  22struct hist_browser {
  23        struct ui_browser   b;
  24        struct hists        *hists;
  25        struct hist_entry   *he_selection;
  26        struct map_symbol   *selection;
  27};
  28
  29static void hist_browser__refresh_dimensions(struct hist_browser *self)
  30{
  31        /* 3 == +/- toggle symbol before actual hist_entry rendering */
  32        self->b.width = 3 + (hists__sort_list_width(self->hists) +
  33                             sizeof("[k]"));
  34}
  35
  36static void hist_browser__reset(struct hist_browser *self)
  37{
  38        self->b.nr_entries = self->hists->nr_entries;
  39        hist_browser__refresh_dimensions(self);
  40        ui_browser__reset_index(&self->b);
  41}
  42
  43static char tree__folded_sign(bool unfolded)
  44{
  45        return unfolded ? '-' : '+';
  46}
  47
  48static char map_symbol__folded(const struct map_symbol *self)
  49{
  50        return self->has_children ? tree__folded_sign(self->unfolded) : ' ';
  51}
  52
  53static char hist_entry__folded(const struct hist_entry *self)
  54{
  55        return map_symbol__folded(&self->ms);
  56}
  57
  58static char callchain_list__folded(const struct callchain_list *self)
  59{
  60        return map_symbol__folded(&self->ms);
  61}
  62
  63static void map_symbol__set_folding(struct map_symbol *self, bool unfold)
  64{
  65        self->unfolded = unfold ? self->has_children : false;
  66}
  67
  68static int callchain_node__count_rows_rb_tree(struct callchain_node *self)
  69{
  70        int n = 0;
  71        struct rb_node *nd;
  72
  73        for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
  74                struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
  75                struct callchain_list *chain;
  76                char folded_sign = ' '; /* No children */
  77
  78                list_for_each_entry(chain, &child->val, list) {
  79                        ++n;
  80                        /* We need this because we may not have children */
  81                        folded_sign = callchain_list__folded(chain);
  82                        if (folded_sign == '+')
  83                                break;
  84                }
  85
  86                if (folded_sign == '-') /* Have children and they're unfolded */
  87                        n += callchain_node__count_rows_rb_tree(child);
  88        }
  89
  90        return n;
  91}
  92
  93static int callchain_node__count_rows(struct callchain_node *node)
  94{
  95        struct callchain_list *chain;
  96        bool unfolded = false;
  97        int n = 0;
  98
  99        list_for_each_entry(chain, &node->val, list) {
 100                ++n;
 101                unfolded = chain->ms.unfolded;
 102        }
 103
 104        if (unfolded)
 105                n += callchain_node__count_rows_rb_tree(node);
 106
 107        return n;
 108}
 109
 110static int callchain__count_rows(struct rb_root *chain)
 111{
 112        struct rb_node *nd;
 113        int n = 0;
 114
 115        for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
 116                struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
 117                n += callchain_node__count_rows(node);
 118        }
 119
 120        return n;
 121}
 122
 123static bool map_symbol__toggle_fold(struct map_symbol *self)
 124{
 125        if (!self->has_children)
 126                return false;
 127
 128        self->unfolded = !self->unfolded;
 129        return true;
 130}
 131
 132static void callchain_node__init_have_children_rb_tree(struct callchain_node *self)
 133{
 134        struct rb_node *nd = rb_first(&self->rb_root);
 135
 136        for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
 137                struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
 138                struct callchain_list *chain;
 139                bool first = true;
 140
 141                list_for_each_entry(chain, &child->val, list) {
 142                        if (first) {
 143                                first = false;
 144                                chain->ms.has_children = chain->list.next != &child->val ||
 145                                                         !RB_EMPTY_ROOT(&child->rb_root);
 146                        } else
 147                                chain->ms.has_children = chain->list.next == &child->val &&
 148                                                         !RB_EMPTY_ROOT(&child->rb_root);
 149                }
 150
 151                callchain_node__init_have_children_rb_tree(child);
 152        }
 153}
 154
 155static void callchain_node__init_have_children(struct callchain_node *self)
 156{
 157        struct callchain_list *chain;
 158
 159        list_for_each_entry(chain, &self->val, list)
 160                chain->ms.has_children = !RB_EMPTY_ROOT(&self->rb_root);
 161
 162        callchain_node__init_have_children_rb_tree(self);
 163}
 164
 165static void callchain__init_have_children(struct rb_root *self)
 166{
 167        struct rb_node *nd;
 168
 169        for (nd = rb_first(self); nd; nd = rb_next(nd)) {
 170                struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
 171                callchain_node__init_have_children(node);
 172        }
 173}
 174
 175static void hist_entry__init_have_children(struct hist_entry *self)
 176{
 177        if (!self->init_have_children) {
 178                self->ms.has_children = !RB_EMPTY_ROOT(&self->sorted_chain);
 179                callchain__init_have_children(&self->sorted_chain);
 180                self->init_have_children = true;
 181        }
 182}
 183
 184static bool hist_browser__toggle_fold(struct hist_browser *self)
 185{
 186        if (map_symbol__toggle_fold(self->selection)) {
 187                struct hist_entry *he = self->he_selection;
 188
 189                hist_entry__init_have_children(he);
 190                self->hists->nr_entries -= he->nr_rows;
 191
 192                if (he->ms.unfolded)
 193                        he->nr_rows = callchain__count_rows(&he->sorted_chain);
 194                else
 195                        he->nr_rows = 0;
 196                self->hists->nr_entries += he->nr_rows;
 197                self->b.nr_entries = self->hists->nr_entries;
 198
 199                return true;
 200        }
 201
 202        /* If it doesn't have children, no toggling performed */
 203        return false;
 204}
 205
 206static int callchain_node__set_folding_rb_tree(struct callchain_node *self, bool unfold)
 207{
 208        int n = 0;
 209        struct rb_node *nd;
 210
 211        for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) {
 212                struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
 213                struct callchain_list *chain;
 214                bool has_children = false;
 215
 216                list_for_each_entry(chain, &child->val, list) {
 217                        ++n;
 218                        map_symbol__set_folding(&chain->ms, unfold);
 219                        has_children = chain->ms.has_children;
 220                }
 221
 222                if (has_children)
 223                        n += callchain_node__set_folding_rb_tree(child, unfold);
 224        }
 225
 226        return n;
 227}
 228
 229static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
 230{
 231        struct callchain_list *chain;
 232        bool has_children = false;
 233        int n = 0;
 234
 235        list_for_each_entry(chain, &node->val, list) {
 236                ++n;
 237                map_symbol__set_folding(&chain->ms, unfold);
 238                has_children = chain->ms.has_children;
 239        }
 240
 241        if (has_children)
 242                n += callchain_node__set_folding_rb_tree(node, unfold);
 243
 244        return n;
 245}
 246
 247static int callchain__set_folding(struct rb_root *chain, bool unfold)
 248{
 249        struct rb_node *nd;
 250        int n = 0;
 251
 252        for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
 253                struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
 254                n += callchain_node__set_folding(node, unfold);
 255        }
 256
 257        return n;
 258}
 259
 260static void hist_entry__set_folding(struct hist_entry *self, bool unfold)
 261{
 262        hist_entry__init_have_children(self);
 263        map_symbol__set_folding(&self->ms, unfold);
 264
 265        if (self->ms.has_children) {
 266                int n = callchain__set_folding(&self->sorted_chain, unfold);
 267                self->nr_rows = unfold ? n : 0;
 268        } else
 269                self->nr_rows = 0;
 270}
 271
 272static void hists__set_folding(struct hists *self, bool unfold)
 273{
 274        struct rb_node *nd;
 275
 276        self->nr_entries = 0;
 277
 278        for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) {
 279                struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
 280                hist_entry__set_folding(he, unfold);
 281                self->nr_entries += 1 + he->nr_rows;
 282        }
 283}
 284
 285static void hist_browser__set_folding(struct hist_browser *self, bool unfold)
 286{
 287        hists__set_folding(self->hists, unfold);
 288        self->b.nr_entries = self->hists->nr_entries;
 289        /* Go to the start, we may be way after valid entries after a collapse */
 290        ui_browser__reset_index(&self->b);
 291}
 292
 293static int hist_browser__run(struct hist_browser *self, const char *title)
 294{
 295        int key;
 296        int exit_keys[] = { 'a', '?', 'h', 'C', 'd', 'D', 'E', 't',
 297                            NEWT_KEY_ENTER, NEWT_KEY_RIGHT, NEWT_KEY_LEFT,
 298                            NEWT_KEY_TAB, NEWT_KEY_UNTAB, 0, };
 299
 300        self->b.entries = &self->hists->entries;
 301        self->b.nr_entries = self->hists->nr_entries;
 302
 303        hist_browser__refresh_dimensions(self);
 304
 305        if (ui_browser__show(&self->b, title,
 306                             "Press '?' for help on key bindings") < 0)
 307                return -1;
 308
 309        ui_browser__add_exit_keys(&self->b, exit_keys);
 310
 311        while (1) {
 312                key = ui_browser__run(&self->b);
 313
 314                switch (key) {
 315                case 'D': { /* Debug */
 316                        static int seq;
 317                        struct hist_entry *h = rb_entry(self->b.top,
 318                                                        struct hist_entry, rb_node);
 319                        ui_helpline__pop();
 320                        ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
 321                                           seq++, self->b.nr_entries,
 322                                           self->hists->nr_entries,
 323                                           self->b.height,
 324                                           self->b.index,
 325                                           self->b.top_idx,
 326                                           h->row_offset, h->nr_rows);
 327                }
 328                        break;
 329                case 'C':
 330                        /* Collapse the whole world. */
 331                        hist_browser__set_folding(self, false);
 332                        break;
 333                case 'E':
 334                        /* Expand the whole world. */
 335                        hist_browser__set_folding(self, true);
 336                        break;
 337                case NEWT_KEY_ENTER:
 338                        if (hist_browser__toggle_fold(self))
 339                                break;
 340                        /* fall thru */
 341                default:
 342                        goto out;
 343                }
 344        }
 345out:
 346        ui_browser__hide(&self->b);
 347        return key;
 348}
 349
 350static char *callchain_list__sym_name(struct callchain_list *self,
 351                                      char *bf, size_t bfsize)
 352{
 353        if (self->ms.sym)
 354                return self->ms.sym->name;
 355
 356        snprintf(bf, bfsize, "%#" PRIx64, self->ip);
 357        return bf;
 358}
 359
 360#define LEVEL_OFFSET_STEP 3
 361
 362static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self,
 363                                                     struct callchain_node *chain_node,
 364                                                     u64 total, int level,
 365                                                     unsigned short row,
 366                                                     off_t *row_offset,
 367                                                     bool *is_current_entry)
 368{
 369        struct rb_node *node;
 370        int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
 371        u64 new_total, remaining;
 372
 373        if (callchain_param.mode == CHAIN_GRAPH_REL)
 374                new_total = chain_node->children_hit;
 375        else
 376                new_total = total;
 377
 378        remaining = new_total;
 379        node = rb_first(&chain_node->rb_root);
 380        while (node) {
 381                struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
 382                struct rb_node *next = rb_next(node);
 383                u64 cumul = callchain_cumul_hits(child);
 384                struct callchain_list *chain;
 385                char folded_sign = ' ';
 386                int first = true;
 387                int extra_offset = 0;
 388
 389                remaining -= cumul;
 390
 391                list_for_each_entry(chain, &child->val, list) {
 392                        char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str;
 393                        const char *str;
 394                        int color;
 395                        bool was_first = first;
 396
 397                        if (first)
 398                                first = false;
 399                        else
 400                                extra_offset = LEVEL_OFFSET_STEP;
 401
 402                        folded_sign = callchain_list__folded(chain);
 403                        if (*row_offset != 0) {
 404                                --*row_offset;
 405                                goto do_next;
 406                        }
 407
 408                        alloc_str = NULL;
 409                        str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
 410                        if (was_first) {
 411                                double percent = cumul * 100.0 / new_total;
 412
 413                                if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
 414                                        str = "Not enough memory!";
 415                                else
 416                                        str = alloc_str;
 417                        }
 418
 419                        color = HE_COLORSET_NORMAL;
 420                        width = self->b.width - (offset + extra_offset + 2);
 421                        if (ui_browser__is_current_entry(&self->b, row)) {
 422                                self->selection = &chain->ms;
 423                                color = HE_COLORSET_SELECTED;
 424                                *is_current_entry = true;
 425                        }
 426
 427                        ui_browser__set_color(&self->b, color);
 428                        ui_browser__gotorc(&self->b, row, 0);
 429                        slsmg_write_nstring(" ", offset + extra_offset);
 430                        slsmg_printf("%c ", folded_sign);
 431                        slsmg_write_nstring(str, width);
 432                        free(alloc_str);
 433
 434                        if (++row == self->b.height)
 435                                goto out;
 436do_next:
 437                        if (folded_sign == '+')
 438                                break;
 439                }
 440
 441                if (folded_sign == '-') {
 442                        const int new_level = level + (extra_offset ? 2 : 1);
 443                        row += hist_browser__show_callchain_node_rb_tree(self, child, new_total,
 444                                                                         new_level, row, row_offset,
 445                                                                         is_current_entry);
 446                }
 447                if (row == self->b.height)
 448                        goto out;
 449                node = next;
 450        }
 451out:
 452        return row - first_row;
 453}
 454
 455static int hist_browser__show_callchain_node(struct hist_browser *self,
 456                                             struct callchain_node *node,
 457                                             int level, unsigned short row,
 458                                             off_t *row_offset,
 459                                             bool *is_current_entry)
 460{
 461        struct callchain_list *chain;
 462        int first_row = row,
 463             offset = level * LEVEL_OFFSET_STEP,
 464             width = self->b.width - offset;
 465        char folded_sign = ' ';
 466
 467        list_for_each_entry(chain, &node->val, list) {
 468                char ipstr[BITS_PER_LONG / 4 + 1], *s;
 469                int color;
 470
 471                folded_sign = callchain_list__folded(chain);
 472
 473                if (*row_offset != 0) {
 474                        --*row_offset;
 475                        continue;
 476                }
 477
 478                color = HE_COLORSET_NORMAL;
 479                if (ui_browser__is_current_entry(&self->b, row)) {
 480                        self->selection = &chain->ms;
 481                        color = HE_COLORSET_SELECTED;
 482                        *is_current_entry = true;
 483                }
 484
 485                s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr));
 486                ui_browser__gotorc(&self->b, row, 0);
 487                ui_browser__set_color(&self->b, color);
 488                slsmg_write_nstring(" ", offset);
 489                slsmg_printf("%c ", folded_sign);
 490                slsmg_write_nstring(s, width - 2);
 491
 492                if (++row == self->b.height)
 493                        goto out;
 494        }
 495
 496        if (folded_sign == '-')
 497                row += hist_browser__show_callchain_node_rb_tree(self, node,
 498                                                                 self->hists->stats.total_period,
 499                                                                 level + 1, row,
 500                                                                 row_offset,
 501                                                                 is_current_entry);
 502out:
 503        return row - first_row;
 504}
 505
 506static int hist_browser__show_callchain(struct hist_browser *self,
 507                                        struct rb_root *chain,
 508                                        int level, unsigned short row,
 509                                        off_t *row_offset,
 510                                        bool *is_current_entry)
 511{
 512        struct rb_node *nd;
 513        int first_row = row;
 514
 515        for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
 516                struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
 517
 518                row += hist_browser__show_callchain_node(self, node, level,
 519                                                         row, row_offset,
 520                                                         is_current_entry);
 521                if (row == self->b.height)
 522                        break;
 523        }
 524
 525        return row - first_row;
 526}
 527
 528static int hist_browser__show_entry(struct hist_browser *self,
 529                                    struct hist_entry *entry,
 530                                    unsigned short row)
 531{
 532        char s[256];
 533        double percent;
 534        int printed = 0;
 535        int color, width = self->b.width;
 536        char folded_sign = ' ';
 537        bool current_entry = ui_browser__is_current_entry(&self->b, row);
 538        off_t row_offset = entry->row_offset;
 539
 540        if (current_entry) {
 541                self->he_selection = entry;
 542                self->selection = &entry->ms;
 543        }
 544
 545        if (symbol_conf.use_callchain) {
 546                hist_entry__init_have_children(entry);
 547                folded_sign = hist_entry__folded(entry);
 548        }
 549
 550        if (row_offset == 0) {
 551                hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false,
 552                                     0, false, self->hists->stats.total_period);
 553                percent = (entry->period * 100.0) / self->hists->stats.total_period;
 554
 555                color = HE_COLORSET_SELECTED;
 556                if (!current_entry) {
 557                        if (percent >= MIN_RED)
 558                                color = HE_COLORSET_TOP;
 559                        else if (percent >= MIN_GREEN)
 560                                color = HE_COLORSET_MEDIUM;
 561                        else
 562                                color = HE_COLORSET_NORMAL;
 563                }
 564
 565                ui_browser__set_color(&self->b, color);
 566                ui_browser__gotorc(&self->b, row, 0);
 567                if (symbol_conf.use_callchain) {
 568                        slsmg_printf("%c ", folded_sign);
 569                        width -= 2;
 570                }
 571                slsmg_write_nstring(s, width);
 572                ++row;
 573                ++printed;
 574        } else
 575                --row_offset;
 576
 577        if (folded_sign == '-' && row != self->b.height) {
 578                printed += hist_browser__show_callchain(self, &entry->sorted_chain,
 579                                                        1, row, &row_offset,
 580                                                        &current_entry);
 581                if (current_entry)
 582                        self->he_selection = entry;
 583        }
 584
 585        return printed;
 586}
 587
 588static unsigned int hist_browser__refresh(struct ui_browser *self)
 589{
 590        unsigned row = 0;
 591        struct rb_node *nd;
 592        struct hist_browser *hb = container_of(self, struct hist_browser, b);
 593
 594        if (self->top == NULL)
 595                self->top = rb_first(&hb->hists->entries);
 596
 597        for (nd = self->top; nd; nd = rb_next(nd)) {
 598                struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
 599
 600                if (h->filtered)
 601                        continue;
 602
 603                row += hist_browser__show_entry(hb, h, row);
 604                if (row == self->height)
 605                        break;
 606        }
 607
 608        return row;
 609}
 610
 611static struct rb_node *hists__filter_entries(struct rb_node *nd)
 612{
 613        while (nd != NULL) {
 614                struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
 615                if (!h->filtered)
 616                        return nd;
 617
 618                nd = rb_next(nd);
 619        }
 620
 621        return NULL;
 622}
 623
 624static struct rb_node *hists__filter_prev_entries(struct rb_node *nd)
 625{
 626        while (nd != NULL) {
 627                struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
 628                if (!h->filtered)
 629                        return nd;
 630
 631                nd = rb_prev(nd);
 632        }
 633
 634        return NULL;
 635}
 636
 637static void ui_browser__hists_seek(struct ui_browser *self,
 638                                   off_t offset, int whence)
 639{
 640        struct hist_entry *h;
 641        struct rb_node *nd;
 642        bool first = true;
 643
 644        if (self->nr_entries == 0)
 645                return;
 646
 647        switch (whence) {
 648        case SEEK_SET:
 649                nd = hists__filter_entries(rb_first(self->entries));
 650                break;
 651        case SEEK_CUR:
 652                nd = self->top;
 653                goto do_offset;
 654        case SEEK_END:
 655                nd = hists__filter_prev_entries(rb_last(self->entries));
 656                first = false;
 657                break;
 658        default:
 659                return;
 660        }
 661
 662        /*
 663         * Moves not relative to the first visible entry invalidates its
 664         * row_offset:
 665         */
 666        h = rb_entry(self->top, struct hist_entry, rb_node);
 667        h->row_offset = 0;
 668
 669        /*
 670         * Here we have to check if nd is expanded (+), if it is we can't go
 671         * the next top level hist_entry, instead we must compute an offset of
 672         * what _not_ to show and not change the first visible entry.
 673         *
 674         * This offset increments when we are going from top to bottom and
 675         * decreases when we're going from bottom to top.
 676         *
 677         * As we don't have backpointers to the top level in the callchains
 678         * structure, we need to always print the whole hist_entry callchain,
 679         * skipping the first ones that are before the first visible entry
 680         * and stop when we printed enough lines to fill the screen.
 681         */
 682do_offset:
 683        if (offset > 0) {
 684                do {
 685                        h = rb_entry(nd, struct hist_entry, rb_node);
 686                        if (h->ms.unfolded) {
 687                                u16 remaining = h->nr_rows - h->row_offset;
 688                                if (offset > remaining) {
 689                                        offset -= remaining;
 690                                        h->row_offset = 0;
 691                                } else {
 692                                        h->row_offset += offset;
 693                                        offset = 0;
 694                                        self->top = nd;
 695                                        break;
 696                                }
 697                        }
 698                        nd = hists__filter_entries(rb_next(nd));
 699                        if (nd == NULL)
 700                                break;
 701                        --offset;
 702                        self->top = nd;
 703                } while (offset != 0);
 704        } else if (offset < 0) {
 705                while (1) {
 706                        h = rb_entry(nd, struct hist_entry, rb_node);
 707                        if (h->ms.unfolded) {
 708                                if (first) {
 709                                        if (-offset > h->row_offset) {
 710                                                offset += h->row_offset;
 711                                                h->row_offset = 0;
 712                                        } else {
 713                                                h->row_offset += offset;
 714                                                offset = 0;
 715                                                self->top = nd;
 716                                                break;
 717                                        }
 718                                } else {
 719                                        if (-offset > h->nr_rows) {
 720                                                offset += h->nr_rows;
 721                                                h->row_offset = 0;
 722                                        } else {
 723                                                h->row_offset = h->nr_rows + offset;
 724                                                offset = 0;
 725                                                self->top = nd;
 726                                                break;
 727                                        }
 728                                }
 729                        }
 730
 731                        nd = hists__filter_prev_entries(rb_prev(nd));
 732                        if (nd == NULL)
 733                                break;
 734                        ++offset;
 735                        self->top = nd;
 736                        if (offset == 0) {
 737                                /*
 738                                 * Last unfiltered hist_entry, check if it is
 739                                 * unfolded, if it is then we should have
 740                                 * row_offset at its last entry.
 741                                 */
 742                                h = rb_entry(nd, struct hist_entry, rb_node);
 743                                if (h->ms.unfolded)
 744                                        h->row_offset = h->nr_rows;
 745                                break;
 746                        }
 747                        first = false;
 748                }
 749        } else {
 750                self->top = nd;
 751                h = rb_entry(nd, struct hist_entry, rb_node);
 752                h->row_offset = 0;
 753        }
 754}
 755
 756static struct hist_browser *hist_browser__new(struct hists *hists)
 757{
 758        struct hist_browser *self = zalloc(sizeof(*self));
 759
 760        if (self) {
 761                self->hists = hists;
 762                self->b.refresh = hist_browser__refresh;
 763                self->b.seek = ui_browser__hists_seek;
 764        }
 765
 766        return self;
 767}
 768
 769static void hist_browser__delete(struct hist_browser *self)
 770{
 771        free(self);
 772}
 773
 774static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self)
 775{
 776        return self->he_selection;
 777}
 778
 779static struct thread *hist_browser__selected_thread(struct hist_browser *self)
 780{
 781        return self->he_selection->thread;
 782}
 783
 784static int hists__browser_title(struct hists *self, char *bf, size_t size,
 785                                const char *ev_name, const struct dso *dso,
 786                                const struct thread *thread)
 787{
 788        char unit;
 789        int printed;
 790        unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE];
 791
 792        nr_events = convert_unit(nr_events, &unit);
 793        printed = snprintf(bf, size, "Events: %lu%c %s", nr_events, unit, ev_name);
 794
 795        if (thread)
 796                printed += snprintf(bf + printed, size - printed,
 797                                    ", Thread: %s(%d)",
 798                                    (thread->comm_set ? thread->comm : ""),
 799                                    thread->pid);
 800        if (dso)
 801                printed += snprintf(bf + printed, size - printed,
 802                                    ", DSO: %s", dso->short_name);
 803        return printed;
 804}
 805
 806static int perf_evsel__hists_browse(struct perf_evsel *evsel,
 807                                    const char *helpline, const char *ev_name,
 808                                    bool left_exits)
 809{
 810        struct hists *self = &evsel->hists;
 811        struct hist_browser *browser = hist_browser__new(self);
 812        struct pstack *fstack;
 813        const struct thread *thread_filter = NULL;
 814        const struct dso *dso_filter = NULL;
 815        char msg[160];
 816        int key = -1;
 817
 818        if (browser == NULL)
 819                return -1;
 820
 821        fstack = pstack__new(2);
 822        if (fstack == NULL)
 823                goto out;
 824
 825        ui_helpline__push(helpline);
 826
 827        hists__browser_title(self, msg, sizeof(msg), ev_name,
 828                             dso_filter, thread_filter);
 829        while (1) {
 830                const struct thread *thread = NULL;
 831                const struct dso *dso = NULL;
 832                char *options[16];
 833                int nr_options = 0, choice = 0, i,
 834                    annotate = -2, zoom_dso = -2, zoom_thread = -2,
 835                    browse_map = -2;
 836
 837                key = hist_browser__run(browser, msg);
 838
 839                if (browser->he_selection != NULL) {
 840                        thread = hist_browser__selected_thread(browser);
 841                        dso = browser->selection->map ? browser->selection->map->dso : NULL;
 842                }
 843
 844                switch (key) {
 845                case NEWT_KEY_TAB:
 846                case NEWT_KEY_UNTAB:
 847                        /*
 848                         * Exit the browser, let hists__browser_tree
 849                         * go to the next or previous
 850                         */
 851                        goto out_free_stack;
 852                case 'a':
 853                        if (browser->selection == NULL ||
 854                            browser->selection->sym == NULL ||
 855                            browser->selection->map->dso->annotate_warned)
 856                                continue;
 857                        goto do_annotate;
 858                case 'd':
 859                        goto zoom_dso;
 860                case 't':
 861                        goto zoom_thread;
 862                case NEWT_KEY_F1:
 863                case 'h':
 864                case '?':
 865                        ui__help_window("->        Zoom into DSO/Threads & Annotate current symbol\n"
 866                                        "<-        Zoom out\n"
 867                                        "a         Annotate current symbol\n"
 868                                        "h/?/F1    Show this window\n"
 869                                        "C         Collapse all callchains\n"
 870                                        "E         Expand all callchains\n"
 871                                        "d         Zoom into current DSO\n"
 872                                        "t         Zoom into current Thread\n"
 873                                        "TAB/UNTAB Switch events\n"
 874                                        "q/CTRL+C  Exit browser");
 875                        continue;
 876                case NEWT_KEY_ENTER:
 877                case NEWT_KEY_RIGHT:
 878                        /* menu */
 879                        break;
 880                case NEWT_KEY_LEFT: {
 881                        const void *top;
 882
 883                        if (pstack__empty(fstack)) {
 884                                /*
 885                                 * Go back to the perf_evsel_menu__run or other user
 886                                 */
 887                                if (left_exits)
 888                                        goto out_free_stack;
 889                                continue;
 890                        }
 891                        top = pstack__pop(fstack);
 892                        if (top == &dso_filter)
 893                                goto zoom_out_dso;
 894                        if (top == &thread_filter)
 895                                goto zoom_out_thread;
 896                        continue;
 897                }
 898                case NEWT_KEY_ESCAPE:
 899                        if (!left_exits &&
 900                            !ui__dialog_yesno("Do you really want to exit?"))
 901                                continue;
 902                        /* Fall thru */
 903                default:
 904                        goto out_free_stack;
 905                }
 906
 907                if (browser->selection != NULL &&
 908                    browser->selection->sym != NULL &&
 909                    !browser->selection->map->dso->annotate_warned &&
 910                    asprintf(&options[nr_options], "Annotate %s",
 911                             browser->selection->sym->name) > 0)
 912                        annotate = nr_options++;
 913
 914                if (thread != NULL &&
 915                    asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
 916                             (thread_filter ? "out of" : "into"),
 917                             (thread->comm_set ? thread->comm : ""),
 918                             thread->pid) > 0)
 919                        zoom_thread = nr_options++;
 920
 921                if (dso != NULL &&
 922                    asprintf(&options[nr_options], "Zoom %s %s DSO",
 923                             (dso_filter ? "out of" : "into"),
 924                             (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
 925                        zoom_dso = nr_options++;
 926
 927                if (browser->selection != NULL &&
 928                    browser->selection->map != NULL &&
 929                    asprintf(&options[nr_options], "Browse map details") > 0)
 930                        browse_map = nr_options++;
 931
 932                options[nr_options++] = (char *)"Exit";
 933
 934                choice = ui__popup_menu(nr_options, options);
 935
 936                for (i = 0; i < nr_options - 1; ++i)
 937                        free(options[i]);
 938
 939                if (choice == nr_options - 1)
 940                        break;
 941
 942                if (choice == -1)
 943                        continue;
 944
 945                if (choice == annotate) {
 946                        struct hist_entry *he;
 947do_annotate:
 948                        he = hist_browser__selected_entry(browser);
 949                        if (he == NULL)
 950                                continue;
 951
 952                        hist_entry__tui_annotate(he, evsel->idx);
 953                } else if (choice == browse_map)
 954                        map__browse(browser->selection->map);
 955                else if (choice == zoom_dso) {
 956zoom_dso:
 957                        if (dso_filter) {
 958                                pstack__remove(fstack, &dso_filter);
 959zoom_out_dso:
 960                                ui_helpline__pop();
 961                                dso_filter = NULL;
 962                        } else {
 963                                if (dso == NULL)
 964                                        continue;
 965                                ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
 966                                                   dso->kernel ? "the Kernel" : dso->short_name);
 967                                dso_filter = dso;
 968                                pstack__push(fstack, &dso_filter);
 969                        }
 970                        hists__filter_by_dso(self, dso_filter);
 971                        hists__browser_title(self, msg, sizeof(msg), ev_name,
 972                                             dso_filter, thread_filter);
 973                        hist_browser__reset(browser);
 974                } else if (choice == zoom_thread) {
 975zoom_thread:
 976                        if (thread_filter) {
 977                                pstack__remove(fstack, &thread_filter);
 978zoom_out_thread:
 979                                ui_helpline__pop();
 980                                thread_filter = NULL;
 981                        } else {
 982                                ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
 983                                                   thread->comm_set ? thread->comm : "",
 984                                                   thread->pid);
 985                                thread_filter = thread;
 986                                pstack__push(fstack, &thread_filter);
 987                        }
 988                        hists__filter_by_thread(self, thread_filter);
 989                        hists__browser_title(self, msg, sizeof(msg), ev_name,
 990                                             dso_filter, thread_filter);
 991                        hist_browser__reset(browser);
 992                }
 993        }
 994out_free_stack:
 995        pstack__delete(fstack);
 996out:
 997        hist_browser__delete(browser);
 998        return key;
 999}
1000
1001struct perf_evsel_menu {
1002        struct ui_browser b;
1003        struct perf_evsel *selection;
1004};
1005
1006static void perf_evsel_menu__write(struct ui_browser *browser,
1007                                   void *entry, int row)
1008{
1009        struct perf_evsel_menu *menu = container_of(browser,
1010                                                    struct perf_evsel_menu, b);
1011        struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
1012        bool current_entry = ui_browser__is_current_entry(browser, row);
1013        unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE];
1014        const char *ev_name = event_name(evsel);
1015        char bf[256], unit;
1016
1017        ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
1018                                                       HE_COLORSET_NORMAL);
1019
1020        nr_events = convert_unit(nr_events, &unit);
1021        snprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
1022                 unit, unit == ' ' ? "" : " ", ev_name);
1023        slsmg_write_nstring(bf, browser->width);
1024
1025        if (current_entry)
1026                menu->selection = evsel;
1027}
1028
1029static int perf_evsel_menu__run(struct perf_evsel_menu *menu, const char *help)
1030{
1031        int exit_keys[] = { NEWT_KEY_ENTER, NEWT_KEY_RIGHT, 0, };
1032        struct perf_evlist *evlist = menu->b.priv;
1033        struct perf_evsel *pos;
1034        const char *ev_name, *title = "Available samples";
1035        int key;
1036
1037        if (ui_browser__show(&menu->b, title,
1038                             "ESC: exit, ENTER|->: Browse histograms") < 0)
1039                return -1;
1040
1041        ui_browser__add_exit_keys(&menu->b, exit_keys);
1042
1043        while (1) {
1044                key = ui_browser__run(&menu->b);
1045
1046                switch (key) {
1047                case NEWT_KEY_RIGHT:
1048                case NEWT_KEY_ENTER:
1049                        if (!menu->selection)
1050                                continue;
1051                        pos = menu->selection;
1052browse_hists:
1053                        ev_name = event_name(pos);
1054                        key = perf_evsel__hists_browse(pos, help, ev_name, true);
1055                        ui_browser__show_title(&menu->b, title);
1056                        break;
1057                case NEWT_KEY_LEFT:
1058                        continue;
1059                case NEWT_KEY_ESCAPE:
1060                        if (!ui__dialog_yesno("Do you really want to exit?"))
1061                                continue;
1062                        /* Fall thru */
1063                default:
1064                        goto out;
1065                }
1066
1067                switch (key) {
1068                case NEWT_KEY_TAB:
1069                        if (pos->node.next == &evlist->entries)
1070                                pos = list_entry(evlist->entries.next, struct perf_evsel, node);
1071                        else
1072                                pos = list_entry(pos->node.next, struct perf_evsel, node);
1073                        goto browse_hists;
1074                case NEWT_KEY_UNTAB:
1075                        if (pos->node.prev == &evlist->entries)
1076                                pos = list_entry(evlist->entries.prev, struct perf_evsel, node);
1077                        else
1078                                pos = list_entry(pos->node.prev, struct perf_evsel, node);
1079                        goto browse_hists;
1080                case 'q':
1081                case CTRL('c'):
1082                        goto out;
1083                default:
1084                        break;
1085                }
1086        }
1087
1088out:
1089        ui_browser__hide(&menu->b);
1090        return key;
1091}
1092
1093static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
1094                                           const char *help)
1095{
1096        struct perf_evsel *pos;
1097        struct perf_evsel_menu menu = {
1098                .b = {
1099                        .entries    = &evlist->entries,
1100                        .refresh    = ui_browser__list_head_refresh,
1101                        .seek       = ui_browser__list_head_seek,
1102                        .write      = perf_evsel_menu__write,
1103                        .nr_entries = evlist->nr_entries,
1104                        .priv       = evlist,
1105                },
1106        };
1107
1108        ui_helpline__push("Press ESC to exit");
1109
1110        list_for_each_entry(pos, &evlist->entries, node) {
1111                const char *ev_name = event_name(pos);
1112                size_t line_len = strlen(ev_name) + 7;
1113
1114                if (menu.b.width < line_len)
1115                        menu.b.width = line_len;
1116                /*
1117                 * Cache the evsel name, tracepoints have a _high_ cost per
1118                 * event_name() call.
1119                 */
1120                if (pos->name == NULL)
1121                        pos->name = strdup(ev_name);
1122        }
1123
1124        return perf_evsel_menu__run(&menu, help);
1125}
1126
1127int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help)
1128{
1129
1130        if (evlist->nr_entries == 1) {
1131                struct perf_evsel *first = list_entry(evlist->entries.next,
1132                                                      struct perf_evsel, node);
1133                const char *ev_name = event_name(first);
1134                return perf_evsel__hists_browse(first, help, ev_name, false);
1135        }
1136
1137        return __perf_evlist__tui_browse_hists(evlist, help);
1138}
1139