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