diff options
Diffstat (limited to 'tools/perf/util/newt.c')
-rw-r--r-- | tools/perf/util/newt.c | 1164 |
1 files changed, 777 insertions, 387 deletions
diff --git a/tools/perf/util/newt.c b/tools/perf/util/newt.c index 7537ca15900b..91de99b58445 100644 --- a/tools/perf/util/newt.c +++ b/tools/perf/util/newt.c | |||
@@ -11,6 +11,7 @@ | |||
11 | #define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG | 11 | #define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG |
12 | #endif | 12 | #endif |
13 | #include <slang.h> | 13 | #include <slang.h> |
14 | #include <signal.h> | ||
14 | #include <stdlib.h> | 15 | #include <stdlib.h> |
15 | #include <newt.h> | 16 | #include <newt.h> |
16 | #include <sys/ttydefaults.h> | 17 | #include <sys/ttydefaults.h> |
@@ -278,9 +279,48 @@ struct ui_browser { | |||
278 | void *first_visible_entry, *entries; | 279 | void *first_visible_entry, *entries; |
279 | u16 top, left, width, height; | 280 | u16 top, left, width, height; |
280 | void *priv; | 281 | void *priv; |
282 | unsigned int (*refresh_entries)(struct ui_browser *self); | ||
283 | void (*seek)(struct ui_browser *self, | ||
284 | off_t offset, int whence); | ||
281 | u32 nr_entries; | 285 | u32 nr_entries; |
282 | }; | 286 | }; |
283 | 287 | ||
288 | static void ui_browser__list_head_seek(struct ui_browser *self, | ||
289 | off_t offset, int whence) | ||
290 | { | ||
291 | struct list_head *head = self->entries; | ||
292 | struct list_head *pos; | ||
293 | |||
294 | switch (whence) { | ||
295 | case SEEK_SET: | ||
296 | pos = head->next; | ||
297 | break; | ||
298 | case SEEK_CUR: | ||
299 | pos = self->first_visible_entry; | ||
300 | break; | ||
301 | case SEEK_END: | ||
302 | pos = head->prev; | ||
303 | break; | ||
304 | default: | ||
305 | return; | ||
306 | } | ||
307 | |||
308 | if (offset > 0) { | ||
309 | while (offset-- != 0) | ||
310 | pos = pos->next; | ||
311 | } else { | ||
312 | while (offset++ != 0) | ||
313 | pos = pos->prev; | ||
314 | } | ||
315 | |||
316 | self->first_visible_entry = pos; | ||
317 | } | ||
318 | |||
319 | static bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row) | ||
320 | { | ||
321 | return (self->first_visible_entry_idx + row) == self->index; | ||
322 | } | ||
323 | |||
284 | static void ui_browser__refresh_dimensions(struct ui_browser *self) | 324 | static void ui_browser__refresh_dimensions(struct ui_browser *self) |
285 | { | 325 | { |
286 | int cols, rows; | 326 | int cols, rows; |
@@ -297,8 +337,36 @@ static void ui_browser__refresh_dimensions(struct ui_browser *self) | |||
297 | 337 | ||
298 | static void ui_browser__reset_index(struct ui_browser *self) | 338 | static void ui_browser__reset_index(struct ui_browser *self) |
299 | { | 339 | { |
300 | self->index = self->first_visible_entry_idx = 0; | 340 | self->index = self->first_visible_entry_idx = 0; |
301 | self->first_visible_entry = NULL; | 341 | self->seek(self, 0, SEEK_SET); |
342 | } | ||
343 | |||
344 | static int ui_browser__show(struct ui_browser *self, const char *title) | ||
345 | { | ||
346 | if (self->form != NULL) { | ||
347 | newtFormDestroy(self->form); | ||
348 | newtPopWindow(); | ||
349 | } | ||
350 | ui_browser__refresh_dimensions(self); | ||
351 | newtCenteredWindow(self->width, self->height, title); | ||
352 | self->form = newt_form__new(); | ||
353 | if (self->form == NULL) | ||
354 | return -1; | ||
355 | |||
356 | self->sb = newtVerticalScrollbar(self->width, 0, self->height, | ||
357 | HE_COLORSET_NORMAL, | ||
358 | HE_COLORSET_SELECTED); | ||
359 | if (self->sb == NULL) | ||
360 | return -1; | ||
361 | |||
362 | newtFormAddHotKey(self->form, NEWT_KEY_UP); | ||
363 | newtFormAddHotKey(self->form, NEWT_KEY_DOWN); | ||
364 | newtFormAddHotKey(self->form, NEWT_KEY_PGUP); | ||
365 | newtFormAddHotKey(self->form, NEWT_KEY_PGDN); | ||
366 | newtFormAddHotKey(self->form, NEWT_KEY_HOME); | ||
367 | newtFormAddHotKey(self->form, NEWT_KEY_END); | ||
368 | newtFormAddComponent(self->form, self->sb); | ||
369 | return 0; | ||
302 | } | 370 | } |
303 | 371 | ||
304 | static int objdump_line__show(struct objdump_line *self, struct list_head *head, | 372 | static int objdump_line__show(struct objdump_line *self, struct list_head *head, |
@@ -352,26 +420,10 @@ static int objdump_line__show(struct objdump_line *self, struct list_head *head, | |||
352 | 420 | ||
353 | static int ui_browser__refresh_entries(struct ui_browser *self) | 421 | static int ui_browser__refresh_entries(struct ui_browser *self) |
354 | { | 422 | { |
355 | struct objdump_line *pos; | 423 | int row; |
356 | struct list_head *head = self->entries; | ||
357 | struct hist_entry *he = self->priv; | ||
358 | int row = 0; | ||
359 | int len = he->ms.sym->end - he->ms.sym->start; | ||
360 | |||
361 | if (self->first_visible_entry == NULL || self->first_visible_entry == self->entries) | ||
362 | self->first_visible_entry = head->next; | ||
363 | |||
364 | pos = list_entry(self->first_visible_entry, struct objdump_line, node); | ||
365 | |||
366 | list_for_each_entry_from(pos, head, node) { | ||
367 | bool current_entry = (self->first_visible_entry_idx + row) == self->index; | ||
368 | SLsmg_gotorc(self->top + row, self->left); | ||
369 | objdump_line__show(pos, head, self->width, | ||
370 | he, len, current_entry); | ||
371 | if (++row == self->height) | ||
372 | break; | ||
373 | } | ||
374 | 424 | ||
425 | newtScrollbarSet(self->sb, self->index, self->nr_entries - 1); | ||
426 | row = self->refresh_entries(self); | ||
375 | SLsmg_set_color(HE_COLORSET_NORMAL); | 427 | SLsmg_set_color(HE_COLORSET_NORMAL); |
376 | SLsmg_fill_region(self->top + row, self->left, | 428 | SLsmg_fill_region(self->top + row, self->left, |
377 | self->height - row, self->width, ' '); | 429 | self->height - row, self->width, ' '); |
@@ -379,42 +431,13 @@ static int ui_browser__refresh_entries(struct ui_browser *self) | |||
379 | return 0; | 431 | return 0; |
380 | } | 432 | } |
381 | 433 | ||
382 | static int ui_browser__run(struct ui_browser *self, const char *title, | 434 | static int ui_browser__run(struct ui_browser *self, struct newtExitStruct *es) |
383 | struct newtExitStruct *es) | ||
384 | { | 435 | { |
385 | if (self->form) { | ||
386 | newtFormDestroy(self->form); | ||
387 | newtPopWindow(); | ||
388 | } | ||
389 | |||
390 | ui_browser__refresh_dimensions(self); | ||
391 | newtCenteredWindow(self->width + 2, self->height, title); | ||
392 | self->form = newt_form__new(); | ||
393 | if (self->form == NULL) | ||
394 | return -1; | ||
395 | |||
396 | self->sb = newtVerticalScrollbar(self->width + 1, 0, self->height, | ||
397 | HE_COLORSET_NORMAL, | ||
398 | HE_COLORSET_SELECTED); | ||
399 | if (self->sb == NULL) | ||
400 | return -1; | ||
401 | |||
402 | newtFormAddHotKey(self->form, NEWT_KEY_UP); | ||
403 | newtFormAddHotKey(self->form, NEWT_KEY_DOWN); | ||
404 | newtFormAddHotKey(self->form, NEWT_KEY_PGUP); | ||
405 | newtFormAddHotKey(self->form, NEWT_KEY_PGDN); | ||
406 | newtFormAddHotKey(self->form, ' '); | ||
407 | newtFormAddHotKey(self->form, NEWT_KEY_HOME); | ||
408 | newtFormAddHotKey(self->form, NEWT_KEY_END); | ||
409 | newtFormAddHotKey(self->form, NEWT_KEY_TAB); | ||
410 | newtFormAddHotKey(self->form, NEWT_KEY_RIGHT); | ||
411 | |||
412 | if (ui_browser__refresh_entries(self) < 0) | 436 | if (ui_browser__refresh_entries(self) < 0) |
413 | return -1; | 437 | return -1; |
414 | newtFormAddComponent(self->form, self->sb); | ||
415 | 438 | ||
416 | while (1) { | 439 | while (1) { |
417 | unsigned int offset; | 440 | off_t offset; |
418 | 441 | ||
419 | newtFormRun(self->form, es); | 442 | newtFormRun(self->form, es); |
420 | 443 | ||
@@ -428,9 +451,8 @@ static int ui_browser__run(struct ui_browser *self, const char *title, | |||
428 | break; | 451 | break; |
429 | ++self->index; | 452 | ++self->index; |
430 | if (self->index == self->first_visible_entry_idx + self->height) { | 453 | if (self->index == self->first_visible_entry_idx + self->height) { |
431 | struct list_head *pos = self->first_visible_entry; | ||
432 | ++self->first_visible_entry_idx; | 454 | ++self->first_visible_entry_idx; |
433 | self->first_visible_entry = pos->next; | 455 | self->seek(self, +1, SEEK_CUR); |
434 | } | 456 | } |
435 | break; | 457 | break; |
436 | case NEWT_KEY_UP: | 458 | case NEWT_KEY_UP: |
@@ -438,9 +460,8 @@ static int ui_browser__run(struct ui_browser *self, const char *title, | |||
438 | break; | 460 | break; |
439 | --self->index; | 461 | --self->index; |
440 | if (self->index < self->first_visible_entry_idx) { | 462 | if (self->index < self->first_visible_entry_idx) { |
441 | struct list_head *pos = self->first_visible_entry; | ||
442 | --self->first_visible_entry_idx; | 463 | --self->first_visible_entry_idx; |
443 | self->first_visible_entry = pos->prev; | 464 | self->seek(self, -1, SEEK_CUR); |
444 | } | 465 | } |
445 | break; | 466 | break; |
446 | case NEWT_KEY_PGDN: | 467 | case NEWT_KEY_PGDN: |
@@ -453,12 +474,7 @@ static int ui_browser__run(struct ui_browser *self, const char *title, | |||
453 | offset = self->nr_entries - 1 - self->index; | 474 | offset = self->nr_entries - 1 - self->index; |
454 | self->index += offset; | 475 | self->index += offset; |
455 | self->first_visible_entry_idx += offset; | 476 | self->first_visible_entry_idx += offset; |
456 | 477 | self->seek(self, +offset, SEEK_CUR); | |
457 | while (offset--) { | ||
458 | struct list_head *pos = self->first_visible_entry; | ||
459 | self->first_visible_entry = pos->next; | ||
460 | } | ||
461 | |||
462 | break; | 478 | break; |
463 | case NEWT_KEY_PGUP: | 479 | case NEWT_KEY_PGUP: |
464 | if (self->first_visible_entry_idx == 0) | 480 | if (self->first_visible_entry_idx == 0) |
@@ -471,36 +487,22 @@ static int ui_browser__run(struct ui_browser *self, const char *title, | |||
471 | 487 | ||
472 | self->index -= offset; | 488 | self->index -= offset; |
473 | self->first_visible_entry_idx -= offset; | 489 | self->first_visible_entry_idx -= offset; |
474 | 490 | self->seek(self, -offset, SEEK_CUR); | |
475 | while (offset--) { | ||
476 | struct list_head *pos = self->first_visible_entry; | ||
477 | self->first_visible_entry = pos->prev; | ||
478 | } | ||
479 | break; | 491 | break; |
480 | case NEWT_KEY_HOME: | 492 | case NEWT_KEY_HOME: |
481 | ui_browser__reset_index(self); | 493 | ui_browser__reset_index(self); |
482 | break; | 494 | break; |
483 | case NEWT_KEY_END: { | 495 | case NEWT_KEY_END: |
484 | struct list_head *head = self->entries; | ||
485 | offset = self->height - 1; | 496 | offset = self->height - 1; |
497 | if (offset >= self->nr_entries) | ||
498 | offset = self->nr_entries - 1; | ||
486 | 499 | ||
487 | if (offset > self->nr_entries) | 500 | self->index = self->nr_entries - 1; |
488 | offset = self->nr_entries; | 501 | self->first_visible_entry_idx = self->index - offset; |
489 | 502 | self->seek(self, -offset, SEEK_END); | |
490 | self->index = self->first_visible_entry_idx = self->nr_entries - 1 - offset; | ||
491 | self->first_visible_entry = head->prev; | ||
492 | while (offset-- != 0) { | ||
493 | struct list_head *pos = self->first_visible_entry; | ||
494 | self->first_visible_entry = pos->prev; | ||
495 | } | ||
496 | } | ||
497 | break; | 503 | break; |
498 | case NEWT_KEY_RIGHT: | ||
499 | case NEWT_KEY_LEFT: | ||
500 | case NEWT_KEY_TAB: | ||
501 | return es->u.key; | ||
502 | default: | 504 | default: |
503 | continue; | 505 | return es->u.key; |
504 | } | 506 | } |
505 | if (ui_browser__refresh_entries(self) < 0) | 507 | if (ui_browser__refresh_entries(self) < 0) |
506 | return -1; | 508 | return -1; |
@@ -508,38 +510,6 @@ static int ui_browser__run(struct ui_browser *self, const char *title, | |||
508 | return 0; | 510 | return 0; |
509 | } | 511 | } |
510 | 512 | ||
511 | /* | ||
512 | * When debugging newt problems it was useful to be able to "unroll" | ||
513 | * the calls to newtCheckBoxTreeAdd{Array,Item}, so that we can generate | ||
514 | * a source file with the sequence of calls to these methods, to then | ||
515 | * tweak the arrays to get the intended results, so I'm keeping this code | ||
516 | * here, may be useful again in the future. | ||
517 | */ | ||
518 | #undef NEWT_DEBUG | ||
519 | |||
520 | static void newt_checkbox_tree__add(newtComponent tree, const char *str, | ||
521 | void *priv, int *indexes) | ||
522 | { | ||
523 | #ifdef NEWT_DEBUG | ||
524 | /* Print the newtCheckboxTreeAddArray to tinker with its index arrays */ | ||
525 | int i = 0, len = 40 - strlen(str); | ||
526 | |||
527 | fprintf(stderr, | ||
528 | "\tnewtCheckboxTreeAddItem(tree, %*.*s\"%s\", (void *)%p, 0, ", | ||
529 | len, len, " ", str, priv); | ||
530 | while (indexes[i] != NEWT_ARG_LAST) { | ||
531 | if (indexes[i] != NEWT_ARG_APPEND) | ||
532 | fprintf(stderr, " %d,", indexes[i]); | ||
533 | else | ||
534 | fprintf(stderr, " %s,", "NEWT_ARG_APPEND"); | ||
535 | ++i; | ||
536 | } | ||
537 | fprintf(stderr, " %s", " NEWT_ARG_LAST);\n"); | ||
538 | fflush(stderr); | ||
539 | #endif | ||
540 | newtCheckboxTreeAddArray(tree, str, priv, 0, indexes); | ||
541 | } | ||
542 | |||
543 | static char *callchain_list__sym_name(struct callchain_list *self, | 513 | static char *callchain_list__sym_name(struct callchain_list *self, |
544 | char *bf, size_t bfsize) | 514 | char *bf, size_t bfsize) |
545 | { | 515 | { |
@@ -550,144 +520,29 @@ static char *callchain_list__sym_name(struct callchain_list *self, | |||
550 | return bf; | 520 | return bf; |
551 | } | 521 | } |
552 | 522 | ||
553 | static void __callchain__append_graph_browser(struct callchain_node *self, | 523 | static unsigned int hist_entry__annotate_browser_refresh(struct ui_browser *self) |
554 | newtComponent tree, u64 total, | ||
555 | int *indexes, int depth) | ||
556 | { | 524 | { |
557 | struct rb_node *node; | 525 | struct objdump_line *pos; |
558 | u64 new_total, remaining; | 526 | struct list_head *head = self->entries; |
559 | int idx = 0; | 527 | struct hist_entry *he = self->priv; |
560 | 528 | int row = 0; | |
561 | if (callchain_param.mode == CHAIN_GRAPH_REL) | 529 | int len = he->ms.sym->end - he->ms.sym->start; |
562 | new_total = self->children_hit; | ||
563 | else | ||
564 | new_total = total; | ||
565 | |||
566 | remaining = new_total; | ||
567 | node = rb_first(&self->rb_root); | ||
568 | while (node) { | ||
569 | struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); | ||
570 | struct rb_node *next = rb_next(node); | ||
571 | u64 cumul = cumul_hits(child); | ||
572 | struct callchain_list *chain; | ||
573 | int first = true, printed = 0; | ||
574 | int chain_idx = -1; | ||
575 | remaining -= cumul; | ||
576 | |||
577 | indexes[depth] = NEWT_ARG_APPEND; | ||
578 | indexes[depth + 1] = NEWT_ARG_LAST; | ||
579 | |||
580 | list_for_each_entry(chain, &child->val, list) { | ||
581 | char ipstr[BITS_PER_LONG / 4 + 1], | ||
582 | *alloc_str = NULL; | ||
583 | const char *str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | ||
584 | |||
585 | if (first) { | ||
586 | double percent = cumul * 100.0 / new_total; | ||
587 | |||
588 | first = false; | ||
589 | if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) | ||
590 | str = "Not enough memory!"; | ||
591 | else | ||
592 | str = alloc_str; | ||
593 | } else { | ||
594 | indexes[depth] = idx; | ||
595 | indexes[depth + 1] = NEWT_ARG_APPEND; | ||
596 | indexes[depth + 2] = NEWT_ARG_LAST; | ||
597 | ++chain_idx; | ||
598 | } | ||
599 | newt_checkbox_tree__add(tree, str, &chain->ms, indexes); | ||
600 | free(alloc_str); | ||
601 | ++printed; | ||
602 | } | ||
603 | |||
604 | indexes[depth] = idx; | ||
605 | if (chain_idx != -1) | ||
606 | indexes[depth + 1] = chain_idx; | ||
607 | if (printed != 0) | ||
608 | ++idx; | ||
609 | __callchain__append_graph_browser(child, tree, new_total, indexes, | ||
610 | depth + (chain_idx != -1 ? 2 : 1)); | ||
611 | node = next; | ||
612 | } | ||
613 | } | ||
614 | |||
615 | static void callchain__append_graph_browser(struct callchain_node *self, | ||
616 | newtComponent tree, u64 total, | ||
617 | int *indexes, int parent_idx) | ||
618 | { | ||
619 | struct callchain_list *chain; | ||
620 | int i = 0; | ||
621 | |||
622 | indexes[1] = NEWT_ARG_APPEND; | ||
623 | indexes[2] = NEWT_ARG_LAST; | ||
624 | |||
625 | list_for_each_entry(chain, &self->val, list) { | ||
626 | char ipstr[BITS_PER_LONG / 4 + 1], *str; | ||
627 | |||
628 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
629 | continue; | ||
630 | |||
631 | if (!i++ && sort__first_dimension == SORT_SYM) | ||
632 | continue; | ||
633 | 530 | ||
634 | str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | 531 | if (self->first_visible_entry == NULL || self->first_visible_entry == self->entries) |
635 | newt_checkbox_tree__add(tree, str, &chain->ms, indexes); | 532 | self->first_visible_entry = head->next; |
636 | } | ||
637 | 533 | ||
638 | indexes[1] = parent_idx; | 534 | pos = list_entry(self->first_visible_entry, struct objdump_line, node); |
639 | indexes[2] = NEWT_ARG_APPEND; | ||
640 | indexes[3] = NEWT_ARG_LAST; | ||
641 | __callchain__append_graph_browser(self, tree, total, indexes, 2); | ||
642 | } | ||
643 | 535 | ||
644 | static void hist_entry__append_callchain_browser(struct hist_entry *self, | 536 | list_for_each_entry_from(pos, head, node) { |
645 | newtComponent tree, u64 total, int parent_idx) | 537 | bool current_entry = ui_browser__is_current_entry(self, row); |
646 | { | 538 | SLsmg_gotorc(self->top + row, self->left); |
647 | struct rb_node *rb_node; | 539 | objdump_line__show(pos, head, self->width, |
648 | int indexes[1024] = { [0] = parent_idx, }; | 540 | he, len, current_entry); |
649 | int idx = 0; | 541 | if (++row == self->height) |
650 | struct callchain_node *chain; | ||
651 | |||
652 | rb_node = rb_first(&self->sorted_chain); | ||
653 | while (rb_node) { | ||
654 | chain = rb_entry(rb_node, struct callchain_node, rb_node); | ||
655 | switch (callchain_param.mode) { | ||
656 | case CHAIN_FLAT: | ||
657 | break; | ||
658 | case CHAIN_GRAPH_ABS: /* falldown */ | ||
659 | case CHAIN_GRAPH_REL: | ||
660 | callchain__append_graph_browser(chain, tree, total, indexes, idx++); | ||
661 | break; | ||
662 | case CHAIN_NONE: | ||
663 | default: | ||
664 | break; | 542 | break; |
665 | } | ||
666 | rb_node = rb_next(rb_node); | ||
667 | } | 543 | } |
668 | } | ||
669 | |||
670 | static size_t hist_entry__append_browser(struct hist_entry *self, | ||
671 | newtComponent tree, u64 total) | ||
672 | { | ||
673 | char s[256]; | ||
674 | size_t ret; | ||
675 | |||
676 | if (symbol_conf.exclude_other && !self->parent) | ||
677 | return 0; | ||
678 | 544 | ||
679 | ret = hist_entry__snprintf(self, s, sizeof(s), NULL, | 545 | return row; |
680 | false, 0, false, total); | ||
681 | if (symbol_conf.use_callchain) { | ||
682 | int indexes[2]; | ||
683 | |||
684 | indexes[0] = NEWT_ARG_APPEND; | ||
685 | indexes[1] = NEWT_ARG_LAST; | ||
686 | newt_checkbox_tree__add(tree, s, &self->ms, indexes); | ||
687 | } else | ||
688 | newtListboxAppendEntry(tree, s, &self->ms); | ||
689 | |||
690 | return ret; | ||
691 | } | 546 | } |
692 | 547 | ||
693 | int hist_entry__tui_annotate(struct hist_entry *self) | 548 | int hist_entry__tui_annotate(struct hist_entry *self) |
@@ -712,7 +567,9 @@ int hist_entry__tui_annotate(struct hist_entry *self) | |||
712 | ui_helpline__push("Press <- or ESC to exit"); | 567 | ui_helpline__push("Press <- or ESC to exit"); |
713 | 568 | ||
714 | memset(&browser, 0, sizeof(browser)); | 569 | memset(&browser, 0, sizeof(browser)); |
715 | browser.entries = &head; | 570 | browser.entries = &head; |
571 | browser.refresh_entries = hist_entry__annotate_browser_refresh; | ||
572 | browser.seek = ui_browser__list_head_seek; | ||
716 | browser.priv = self; | 573 | browser.priv = self; |
717 | list_for_each_entry(pos, &head, node) { | 574 | list_for_each_entry(pos, &head, node) { |
718 | size_t line_len = strlen(pos->line); | 575 | size_t line_len = strlen(pos->line); |
@@ -722,7 +579,9 @@ int hist_entry__tui_annotate(struct hist_entry *self) | |||
722 | } | 579 | } |
723 | 580 | ||
724 | browser.width += 18; /* Percentage */ | 581 | browser.width += 18; /* Percentage */ |
725 | ret = ui_browser__run(&browser, self->ms.sym->name, &es); | 582 | ui_browser__show(&browser, self->ms.sym->name); |
583 | newtFormAddHotKey(browser.form, ' '); | ||
584 | ret = ui_browser__run(&browser, &es); | ||
726 | newtFormDestroy(browser.form); | 585 | newtFormDestroy(browser.form); |
727 | newtPopWindow(); | 586 | newtPopWindow(); |
728 | list_for_each_entry_safe(pos, n, &head, node) { | 587 | list_for_each_entry_safe(pos, n, &head, node) { |
@@ -733,157 +592,48 @@ int hist_entry__tui_annotate(struct hist_entry *self) | |||
733 | return ret; | 592 | return ret; |
734 | } | 593 | } |
735 | 594 | ||
736 | static const void *newt__symbol_tree_get_current(newtComponent self) | ||
737 | { | ||
738 | if (symbol_conf.use_callchain) | ||
739 | return newtCheckboxTreeGetCurrent(self); | ||
740 | return newtListboxGetCurrent(self); | ||
741 | } | ||
742 | |||
743 | static void hist_browser__selection(newtComponent self, void *data) | ||
744 | { | ||
745 | const struct map_symbol **symbol_ptr = data; | ||
746 | *symbol_ptr = newt__symbol_tree_get_current(self); | ||
747 | } | ||
748 | |||
749 | struct hist_browser { | 595 | struct hist_browser { |
750 | newtComponent form, tree; | 596 | struct ui_browser b; |
751 | const struct map_symbol *selection; | 597 | struct hists *hists; |
598 | struct hist_entry *he_selection; | ||
599 | struct map_symbol *selection; | ||
752 | }; | 600 | }; |
753 | 601 | ||
754 | static struct hist_browser *hist_browser__new(void) | 602 | static void hist_browser__reset(struct hist_browser *self); |
603 | static int hist_browser__run(struct hist_browser *self, const char *title, | ||
604 | struct newtExitStruct *es); | ||
605 | static unsigned int hist_browser__refresh_entries(struct ui_browser *self); | ||
606 | static void ui_browser__hists_seek(struct ui_browser *self, | ||
607 | off_t offset, int whence); | ||
608 | |||
609 | static struct hist_browser *hist_browser__new(struct hists *hists) | ||
755 | { | 610 | { |
756 | struct hist_browser *self = malloc(sizeof(*self)); | 611 | struct hist_browser *self = zalloc(sizeof(*self)); |
757 | 612 | ||
758 | if (self != NULL) | 613 | if (self) { |
759 | self->form = NULL; | 614 | self->hists = hists; |
615 | self->b.refresh_entries = hist_browser__refresh_entries; | ||
616 | self->b.seek = ui_browser__hists_seek; | ||
617 | } | ||
760 | 618 | ||
761 | return self; | 619 | return self; |
762 | } | 620 | } |
763 | 621 | ||
764 | static void hist_browser__delete(struct hist_browser *self) | 622 | static void hist_browser__delete(struct hist_browser *self) |
765 | { | 623 | { |
766 | newtFormDestroy(self->form); | 624 | newtFormDestroy(self->b.form); |
767 | newtPopWindow(); | 625 | newtPopWindow(); |
768 | free(self); | 626 | free(self); |
769 | } | 627 | } |
770 | 628 | ||
771 | static int hist_browser__populate(struct hist_browser *self, struct hists *hists, | ||
772 | const char *title) | ||
773 | { | ||
774 | int max_len = 0, idx, cols, rows; | ||
775 | struct ui_progress *progress; | ||
776 | struct rb_node *nd; | ||
777 | u64 curr_hist = 0; | ||
778 | char seq[] = ".", unit; | ||
779 | char str[256]; | ||
780 | unsigned long nr_events = hists->stats.nr_events[PERF_RECORD_SAMPLE]; | ||
781 | |||
782 | if (self->form) { | ||
783 | newtFormDestroy(self->form); | ||
784 | newtPopWindow(); | ||
785 | } | ||
786 | |||
787 | nr_events = convert_unit(nr_events, &unit); | ||
788 | snprintf(str, sizeof(str), "Events: %lu%c ", | ||
789 | nr_events, unit); | ||
790 | newtDrawRootText(0, 0, str); | ||
791 | |||
792 | newtGetScreenSize(NULL, &rows); | ||
793 | |||
794 | if (symbol_conf.use_callchain) | ||
795 | self->tree = newtCheckboxTreeMulti(0, 0, rows - 5, seq, | ||
796 | NEWT_FLAG_SCROLL); | ||
797 | else | ||
798 | self->tree = newtListbox(0, 0, rows - 5, | ||
799 | (NEWT_FLAG_SCROLL | | ||
800 | NEWT_FLAG_RETURNEXIT)); | ||
801 | |||
802 | newtComponentAddCallback(self->tree, hist_browser__selection, | ||
803 | &self->selection); | ||
804 | |||
805 | progress = ui_progress__new("Adding entries to the browser...", | ||
806 | hists->nr_entries); | ||
807 | if (progress == NULL) | ||
808 | return -1; | ||
809 | |||
810 | idx = 0; | ||
811 | for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { | ||
812 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
813 | int len; | ||
814 | |||
815 | if (h->filtered) | ||
816 | continue; | ||
817 | |||
818 | len = hist_entry__append_browser(h, self->tree, hists->stats.total_period); | ||
819 | if (len > max_len) | ||
820 | max_len = len; | ||
821 | if (symbol_conf.use_callchain) | ||
822 | hist_entry__append_callchain_browser(h, self->tree, | ||
823 | hists->stats.total_period, idx++); | ||
824 | ++curr_hist; | ||
825 | if (curr_hist % 5) | ||
826 | ui_progress__update(progress, curr_hist); | ||
827 | } | ||
828 | |||
829 | ui_progress__delete(progress); | ||
830 | |||
831 | newtGetScreenSize(&cols, &rows); | ||
832 | |||
833 | if (max_len > cols) | ||
834 | max_len = cols - 3; | ||
835 | |||
836 | if (!symbol_conf.use_callchain) | ||
837 | newtListboxSetWidth(self->tree, max_len); | ||
838 | |||
839 | newtCenteredWindow(max_len + (symbol_conf.use_callchain ? 5 : 0), | ||
840 | rows - 5, title); | ||
841 | self->form = newt_form__new(); | ||
842 | if (self->form == NULL) | ||
843 | return -1; | ||
844 | |||
845 | newtFormAddHotKey(self->form, 'A'); | ||
846 | newtFormAddHotKey(self->form, 'a'); | ||
847 | newtFormAddHotKey(self->form, 'D'); | ||
848 | newtFormAddHotKey(self->form, 'd'); | ||
849 | newtFormAddHotKey(self->form, 'T'); | ||
850 | newtFormAddHotKey(self->form, 't'); | ||
851 | newtFormAddHotKey(self->form, '?'); | ||
852 | newtFormAddHotKey(self->form, 'H'); | ||
853 | newtFormAddHotKey(self->form, 'h'); | ||
854 | newtFormAddHotKey(self->form, NEWT_KEY_F1); | ||
855 | newtFormAddHotKey(self->form, NEWT_KEY_RIGHT); | ||
856 | newtFormAddHotKey(self->form, NEWT_KEY_TAB); | ||
857 | newtFormAddHotKey(self->form, NEWT_KEY_UNTAB); | ||
858 | newtFormAddComponents(self->form, self->tree, NULL); | ||
859 | self->selection = newt__symbol_tree_get_current(self->tree); | ||
860 | |||
861 | return 0; | ||
862 | } | ||
863 | |||
864 | static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) | 629 | static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) |
865 | { | 630 | { |
866 | int *indexes; | 631 | return self->he_selection; |
867 | |||
868 | if (!symbol_conf.use_callchain) | ||
869 | goto out; | ||
870 | |||
871 | indexes = newtCheckboxTreeFindItem(self->tree, (void *)self->selection); | ||
872 | if (indexes) { | ||
873 | bool is_hist_entry = indexes[1] == NEWT_ARG_LAST; | ||
874 | free(indexes); | ||
875 | if (is_hist_entry) | ||
876 | goto out; | ||
877 | } | ||
878 | return NULL; | ||
879 | out: | ||
880 | return container_of(self->selection, struct hist_entry, ms); | ||
881 | } | 632 | } |
882 | 633 | ||
883 | static struct thread *hist_browser__selected_thread(struct hist_browser *self) | 634 | static struct thread *hist_browser__selected_thread(struct hist_browser *self) |
884 | { | 635 | { |
885 | struct hist_entry *he = hist_browser__selected_entry(self); | 636 | return self->he_selection->thread; |
886 | return he ? he->thread : NULL; | ||
887 | } | 637 | } |
888 | 638 | ||
889 | static int hist_browser__title(char *bf, size_t size, const char *ev_name, | 639 | static int hist_browser__title(char *bf, size_t size, const char *ev_name, |
@@ -905,7 +655,7 @@ static int hist_browser__title(char *bf, size_t size, const char *ev_name, | |||
905 | 655 | ||
906 | int hists__browse(struct hists *self, const char *helpline, const char *ev_name) | 656 | int hists__browse(struct hists *self, const char *helpline, const char *ev_name) |
907 | { | 657 | { |
908 | struct hist_browser *browser = hist_browser__new(); | 658 | struct hist_browser *browser = hist_browser__new(self); |
909 | struct pstack *fstack; | 659 | struct pstack *fstack; |
910 | const struct thread *thread_filter = NULL; | 660 | const struct thread *thread_filter = NULL; |
911 | const struct dso *dso_filter = NULL; | 661 | const struct dso *dso_filter = NULL; |
@@ -924,8 +674,6 @@ int hists__browse(struct hists *self, const char *helpline, const char *ev_name) | |||
924 | 674 | ||
925 | hist_browser__title(msg, sizeof(msg), ev_name, | 675 | hist_browser__title(msg, sizeof(msg), ev_name, |
926 | dso_filter, thread_filter); | 676 | dso_filter, thread_filter); |
927 | if (hist_browser__populate(browser, self, msg) < 0) | ||
928 | goto out_free_stack; | ||
929 | 677 | ||
930 | while (1) { | 678 | while (1) { |
931 | const struct thread *thread; | 679 | const struct thread *thread; |
@@ -934,7 +682,8 @@ int hists__browse(struct hists *self, const char *helpline, const char *ev_name) | |||
934 | int nr_options = 0, choice = 0, i, | 682 | int nr_options = 0, choice = 0, i, |
935 | annotate = -2, zoom_dso = -2, zoom_thread = -2; | 683 | annotate = -2, zoom_dso = -2, zoom_thread = -2; |
936 | 684 | ||
937 | newtFormRun(browser->form, &es); | 685 | if (hist_browser__run(browser, msg, &es)) |
686 | break; | ||
938 | 687 | ||
939 | thread = hist_browser__selected_thread(browser); | 688 | thread = hist_browser__selected_thread(browser); |
940 | dso = browser->selection->map ? browser->selection->map->dso : NULL; | 689 | dso = browser->selection->map ? browser->selection->map->dso : NULL; |
@@ -1069,8 +818,7 @@ zoom_out_dso: | |||
1069 | hists__filter_by_dso(self, dso_filter); | 818 | hists__filter_by_dso(self, dso_filter); |
1070 | hist_browser__title(msg, sizeof(msg), ev_name, | 819 | hist_browser__title(msg, sizeof(msg), ev_name, |
1071 | dso_filter, thread_filter); | 820 | dso_filter, thread_filter); |
1072 | if (hist_browser__populate(browser, self, msg) < 0) | 821 | hist_browser__reset(browser); |
1073 | goto out; | ||
1074 | } else if (choice == zoom_thread) { | 822 | } else if (choice == zoom_thread) { |
1075 | zoom_thread: | 823 | zoom_thread: |
1076 | if (thread_filter) { | 824 | if (thread_filter) { |
@@ -1088,8 +836,7 @@ zoom_out_thread: | |||
1088 | hists__filter_by_thread(self, thread_filter); | 836 | hists__filter_by_thread(self, thread_filter); |
1089 | hist_browser__title(msg, sizeof(msg), ev_name, | 837 | hist_browser__title(msg, sizeof(msg), ev_name, |
1090 | dso_filter, thread_filter); | 838 | dso_filter, thread_filter); |
1091 | if (hist_browser__populate(browser, self, msg) < 0) | 839 | hist_browser__reset(browser); |
1092 | goto out; | ||
1093 | } | 840 | } |
1094 | } | 841 | } |
1095 | out_free_stack: | 842 | out_free_stack: |
@@ -1145,6 +892,13 @@ static struct newtPercentTreeColors { | |||
1145 | "blue", "lightgray", | 892 | "blue", "lightgray", |
1146 | }; | 893 | }; |
1147 | 894 | ||
895 | static void newt_suspend(void *d __used) | ||
896 | { | ||
897 | newtSuspend(); | ||
898 | raise(SIGTSTP); | ||
899 | newtResume(); | ||
900 | } | ||
901 | |||
1148 | void setup_browser(void) | 902 | void setup_browser(void) |
1149 | { | 903 | { |
1150 | struct newtPercentTreeColors *c = &defaultPercentTreeColors; | 904 | struct newtPercentTreeColors *c = &defaultPercentTreeColors; |
@@ -1158,6 +912,7 @@ void setup_browser(void) | |||
1158 | use_browser = 1; | 912 | use_browser = 1; |
1159 | newtInit(); | 913 | newtInit(); |
1160 | newtCls(); | 914 | newtCls(); |
915 | newtSetSuspendCallback(newt_suspend, NULL); | ||
1161 | ui_helpline__puts(" "); | 916 | ui_helpline__puts(" "); |
1162 | sltt_set_color(HE_COLORSET_TOP, NULL, c->topColorFg, c->topColorBg); | 917 | sltt_set_color(HE_COLORSET_TOP, NULL, c->topColorFg, c->topColorBg); |
1163 | sltt_set_color(HE_COLORSET_MEDIUM, NULL, c->mediumColorFg, c->mediumColorBg); | 918 | sltt_set_color(HE_COLORSET_MEDIUM, NULL, c->mediumColorFg, c->mediumColorBg); |
@@ -1176,3 +931,638 @@ void exit_browser(bool wait_for_ok) | |||
1176 | newtFinished(); | 931 | newtFinished(); |
1177 | } | 932 | } |
1178 | } | 933 | } |
934 | |||
935 | static void hist_browser__refresh_dimensions(struct hist_browser *self) | ||
936 | { | ||
937 | /* 3 == +/- toggle symbol before actual hist_entry rendering */ | ||
938 | self->b.width = 3 + (hists__sort_list_width(self->hists) + | ||
939 | sizeof("[k]")); | ||
940 | } | ||
941 | |||
942 | static void hist_browser__reset(struct hist_browser *self) | ||
943 | { | ||
944 | self->b.nr_entries = self->hists->nr_entries; | ||
945 | hist_browser__refresh_dimensions(self); | ||
946 | ui_browser__reset_index(&self->b); | ||
947 | } | ||
948 | |||
949 | static char tree__folded_sign(bool unfolded) | ||
950 | { | ||
951 | return unfolded ? '-' : '+'; | ||
952 | } | ||
953 | |||
954 | static char map_symbol__folded(const struct map_symbol *self) | ||
955 | { | ||
956 | return self->has_children ? tree__folded_sign(self->unfolded) : ' '; | ||
957 | } | ||
958 | |||
959 | static char hist_entry__folded(const struct hist_entry *self) | ||
960 | { | ||
961 | return map_symbol__folded(&self->ms); | ||
962 | } | ||
963 | |||
964 | static char callchain_list__folded(const struct callchain_list *self) | ||
965 | { | ||
966 | return map_symbol__folded(&self->ms); | ||
967 | } | ||
968 | |||
969 | static bool map_symbol__toggle_fold(struct map_symbol *self) | ||
970 | { | ||
971 | if (!self->has_children) | ||
972 | return false; | ||
973 | |||
974 | self->unfolded = !self->unfolded; | ||
975 | return true; | ||
976 | } | ||
977 | |||
978 | #define LEVEL_OFFSET_STEP 3 | ||
979 | |||
980 | static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self, | ||
981 | struct callchain_node *chain_node, | ||
982 | u64 total, int level, | ||
983 | unsigned short row, | ||
984 | off_t *row_offset, | ||
985 | bool *is_current_entry) | ||
986 | { | ||
987 | struct rb_node *node; | ||
988 | int first_row = row, width, offset = level * LEVEL_OFFSET_STEP; | ||
989 | u64 new_total, remaining; | ||
990 | |||
991 | if (callchain_param.mode == CHAIN_GRAPH_REL) | ||
992 | new_total = chain_node->children_hit; | ||
993 | else | ||
994 | new_total = total; | ||
995 | |||
996 | remaining = new_total; | ||
997 | node = rb_first(&chain_node->rb_root); | ||
998 | while (node) { | ||
999 | struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); | ||
1000 | struct rb_node *next = rb_next(node); | ||
1001 | u64 cumul = cumul_hits(child); | ||
1002 | struct callchain_list *chain; | ||
1003 | char folded_sign = ' '; | ||
1004 | int first = true; | ||
1005 | int extra_offset = 0; | ||
1006 | |||
1007 | remaining -= cumul; | ||
1008 | |||
1009 | list_for_each_entry(chain, &child->val, list) { | ||
1010 | char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str; | ||
1011 | const char *str; | ||
1012 | int color; | ||
1013 | bool was_first = first; | ||
1014 | |||
1015 | if (first) { | ||
1016 | first = false; | ||
1017 | chain->ms.has_children = chain->list.next != &child->val || | ||
1018 | rb_first(&child->rb_root) != NULL; | ||
1019 | } else { | ||
1020 | extra_offset = LEVEL_OFFSET_STEP; | ||
1021 | chain->ms.has_children = chain->list.next == &child->val && | ||
1022 | rb_first(&child->rb_root) != NULL; | ||
1023 | } | ||
1024 | |||
1025 | folded_sign = callchain_list__folded(chain); | ||
1026 | if (*row_offset != 0) { | ||
1027 | --*row_offset; | ||
1028 | goto do_next; | ||
1029 | } | ||
1030 | |||
1031 | alloc_str = NULL; | ||
1032 | str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | ||
1033 | if (was_first) { | ||
1034 | double percent = cumul * 100.0 / new_total; | ||
1035 | |||
1036 | if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) | ||
1037 | str = "Not enough memory!"; | ||
1038 | else | ||
1039 | str = alloc_str; | ||
1040 | } | ||
1041 | |||
1042 | color = HE_COLORSET_NORMAL; | ||
1043 | width = self->b.width - (offset + extra_offset + 2); | ||
1044 | if (ui_browser__is_current_entry(&self->b, row)) { | ||
1045 | self->selection = &chain->ms; | ||
1046 | color = HE_COLORSET_SELECTED; | ||
1047 | *is_current_entry = true; | ||
1048 | } | ||
1049 | |||
1050 | SLsmg_set_color(color); | ||
1051 | SLsmg_gotorc(self->b.top + row, self->b.left); | ||
1052 | slsmg_write_nstring(" ", offset + extra_offset); | ||
1053 | slsmg_printf("%c ", folded_sign); | ||
1054 | slsmg_write_nstring(str, width); | ||
1055 | free(alloc_str); | ||
1056 | |||
1057 | if (++row == self->b.height) | ||
1058 | goto out; | ||
1059 | do_next: | ||
1060 | if (folded_sign == '+') | ||
1061 | break; | ||
1062 | } | ||
1063 | |||
1064 | if (folded_sign == '-') { | ||
1065 | const int new_level = level + (extra_offset ? 2 : 1); | ||
1066 | row += hist_browser__show_callchain_node_rb_tree(self, child, new_total, | ||
1067 | new_level, row, row_offset, | ||
1068 | is_current_entry); | ||
1069 | } | ||
1070 | if (row == self->b.height) | ||
1071 | goto out; | ||
1072 | node = next; | ||
1073 | } | ||
1074 | out: | ||
1075 | return row - first_row; | ||
1076 | } | ||
1077 | |||
1078 | static int hist_browser__show_callchain_node(struct hist_browser *self, | ||
1079 | struct callchain_node *node, | ||
1080 | int level, unsigned short row, | ||
1081 | off_t *row_offset, | ||
1082 | bool *is_current_entry) | ||
1083 | { | ||
1084 | struct callchain_list *chain; | ||
1085 | int first_row = row, | ||
1086 | offset = level * LEVEL_OFFSET_STEP, | ||
1087 | width = self->b.width - offset; | ||
1088 | char folded_sign = ' '; | ||
1089 | |||
1090 | list_for_each_entry(chain, &node->val, list) { | ||
1091 | char ipstr[BITS_PER_LONG / 4 + 1], *s; | ||
1092 | int color; | ||
1093 | /* | ||
1094 | * FIXME: This should be moved to somewhere else, | ||
1095 | * probably when the callchain is created, so as not to | ||
1096 | * traverse it all over again | ||
1097 | */ | ||
1098 | chain->ms.has_children = rb_first(&node->rb_root) != NULL; | ||
1099 | folded_sign = callchain_list__folded(chain); | ||
1100 | |||
1101 | if (*row_offset != 0) { | ||
1102 | --*row_offset; | ||
1103 | continue; | ||
1104 | } | ||
1105 | |||
1106 | color = HE_COLORSET_NORMAL; | ||
1107 | if (ui_browser__is_current_entry(&self->b, row)) { | ||
1108 | self->selection = &chain->ms; | ||
1109 | color = HE_COLORSET_SELECTED; | ||
1110 | *is_current_entry = true; | ||
1111 | } | ||
1112 | |||
1113 | s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | ||
1114 | SLsmg_gotorc(self->b.top + row, self->b.left); | ||
1115 | SLsmg_set_color(color); | ||
1116 | slsmg_write_nstring(" ", offset); | ||
1117 | slsmg_printf("%c ", folded_sign); | ||
1118 | slsmg_write_nstring(s, width - 2); | ||
1119 | |||
1120 | if (++row == self->b.height) | ||
1121 | goto out; | ||
1122 | } | ||
1123 | |||
1124 | if (folded_sign == '-') | ||
1125 | row += hist_browser__show_callchain_node_rb_tree(self, node, | ||
1126 | self->hists->stats.total_period, | ||
1127 | level + 1, row, | ||
1128 | row_offset, | ||
1129 | is_current_entry); | ||
1130 | out: | ||
1131 | return row - first_row; | ||
1132 | } | ||
1133 | |||
1134 | static int hist_browser__show_callchain(struct hist_browser *self, | ||
1135 | struct rb_root *chain, | ||
1136 | int level, unsigned short row, | ||
1137 | off_t *row_offset, | ||
1138 | bool *is_current_entry) | ||
1139 | { | ||
1140 | struct rb_node *nd; | ||
1141 | int first_row = row; | ||
1142 | |||
1143 | for (nd = rb_first(chain); nd; nd = rb_next(nd)) { | ||
1144 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
1145 | |||
1146 | row += hist_browser__show_callchain_node(self, node, level, | ||
1147 | row, row_offset, | ||
1148 | is_current_entry); | ||
1149 | if (row == self->b.height) | ||
1150 | break; | ||
1151 | } | ||
1152 | |||
1153 | return row - first_row; | ||
1154 | } | ||
1155 | |||
1156 | static int hist_browser__show_entry(struct hist_browser *self, | ||
1157 | struct hist_entry *entry, | ||
1158 | unsigned short row) | ||
1159 | { | ||
1160 | char s[256]; | ||
1161 | double percent; | ||
1162 | int printed = 0; | ||
1163 | int color, width = self->b.width; | ||
1164 | char folded_sign = ' '; | ||
1165 | bool current_entry = ui_browser__is_current_entry(&self->b, row); | ||
1166 | off_t row_offset = entry->row_offset; | ||
1167 | |||
1168 | if (current_entry) { | ||
1169 | self->he_selection = entry; | ||
1170 | self->selection = &entry->ms; | ||
1171 | } | ||
1172 | |||
1173 | if (symbol_conf.use_callchain) { | ||
1174 | entry->ms.has_children = !RB_EMPTY_ROOT(&entry->sorted_chain); | ||
1175 | folded_sign = hist_entry__folded(entry); | ||
1176 | } | ||
1177 | |||
1178 | if (row_offset == 0) { | ||
1179 | hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false, | ||
1180 | 0, false, self->hists->stats.total_period); | ||
1181 | percent = (entry->period * 100.0) / self->hists->stats.total_period; | ||
1182 | |||
1183 | color = HE_COLORSET_SELECTED; | ||
1184 | if (!current_entry) { | ||
1185 | if (percent >= MIN_RED) | ||
1186 | color = HE_COLORSET_TOP; | ||
1187 | else if (percent >= MIN_GREEN) | ||
1188 | color = HE_COLORSET_MEDIUM; | ||
1189 | else | ||
1190 | color = HE_COLORSET_NORMAL; | ||
1191 | } | ||
1192 | |||
1193 | SLsmg_set_color(color); | ||
1194 | SLsmg_gotorc(self->b.top + row, self->b.left); | ||
1195 | if (symbol_conf.use_callchain) { | ||
1196 | slsmg_printf("%c ", folded_sign); | ||
1197 | width -= 2; | ||
1198 | } | ||
1199 | slsmg_write_nstring(s, width); | ||
1200 | ++row; | ||
1201 | ++printed; | ||
1202 | } else | ||
1203 | --row_offset; | ||
1204 | |||
1205 | if (folded_sign == '-' && row != self->b.height) { | ||
1206 | printed += hist_browser__show_callchain(self, &entry->sorted_chain, | ||
1207 | 1, row, &row_offset, | ||
1208 | ¤t_entry); | ||
1209 | if (current_entry) | ||
1210 | self->he_selection = entry; | ||
1211 | } | ||
1212 | |||
1213 | return printed; | ||
1214 | } | ||
1215 | |||
1216 | static unsigned int hist_browser__refresh_entries(struct ui_browser *self) | ||
1217 | { | ||
1218 | unsigned row = 0; | ||
1219 | struct rb_node *nd; | ||
1220 | struct hist_browser *hb = container_of(self, struct hist_browser, b); | ||
1221 | |||
1222 | if (self->first_visible_entry == NULL) | ||
1223 | self->first_visible_entry = rb_first(&hb->hists->entries); | ||
1224 | |||
1225 | for (nd = self->first_visible_entry; nd; nd = rb_next(nd)) { | ||
1226 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
1227 | |||
1228 | if (h->filtered) | ||
1229 | continue; | ||
1230 | |||
1231 | row += hist_browser__show_entry(hb, h, row); | ||
1232 | if (row == self->height) | ||
1233 | break; | ||
1234 | } | ||
1235 | |||
1236 | return row; | ||
1237 | } | ||
1238 | |||
1239 | static void callchain_node__init_have_children_rb_tree(struct callchain_node *self) | ||
1240 | { | ||
1241 | struct rb_node *nd = rb_first(&self->rb_root); | ||
1242 | |||
1243 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | ||
1244 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | ||
1245 | struct callchain_list *chain; | ||
1246 | int first = true; | ||
1247 | |||
1248 | list_for_each_entry(chain, &child->val, list) { | ||
1249 | if (first) { | ||
1250 | first = false; | ||
1251 | chain->ms.has_children = chain->list.next != &child->val || | ||
1252 | rb_first(&child->rb_root) != NULL; | ||
1253 | } else | ||
1254 | chain->ms.has_children = chain->list.next == &child->val && | ||
1255 | rb_first(&child->rb_root) != NULL; | ||
1256 | } | ||
1257 | |||
1258 | callchain_node__init_have_children_rb_tree(child); | ||
1259 | } | ||
1260 | } | ||
1261 | |||
1262 | static void callchain_node__init_have_children(struct callchain_node *self) | ||
1263 | { | ||
1264 | struct callchain_list *chain; | ||
1265 | |||
1266 | list_for_each_entry(chain, &self->val, list) | ||
1267 | chain->ms.has_children = rb_first(&self->rb_root) != NULL; | ||
1268 | |||
1269 | callchain_node__init_have_children_rb_tree(self); | ||
1270 | } | ||
1271 | |||
1272 | static void callchain__init_have_children(struct rb_root *self) | ||
1273 | { | ||
1274 | struct rb_node *nd; | ||
1275 | |||
1276 | for (nd = rb_first(self); nd; nd = rb_next(nd)) { | ||
1277 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
1278 | callchain_node__init_have_children(node); | ||
1279 | } | ||
1280 | } | ||
1281 | |||
1282 | static void hist_entry__init_have_children(struct hist_entry *self) | ||
1283 | { | ||
1284 | if (!self->init_have_children) { | ||
1285 | callchain__init_have_children(&self->sorted_chain); | ||
1286 | self->init_have_children = true; | ||
1287 | } | ||
1288 | } | ||
1289 | |||
1290 | static struct rb_node *hists__filter_entries(struct rb_node *nd) | ||
1291 | { | ||
1292 | while (nd != NULL) { | ||
1293 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
1294 | if (!h->filtered) | ||
1295 | return nd; | ||
1296 | |||
1297 | nd = rb_next(nd); | ||
1298 | } | ||
1299 | |||
1300 | return NULL; | ||
1301 | } | ||
1302 | |||
1303 | static struct rb_node *hists__filter_prev_entries(struct rb_node *nd) | ||
1304 | { | ||
1305 | while (nd != NULL) { | ||
1306 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
1307 | if (!h->filtered) | ||
1308 | return nd; | ||
1309 | |||
1310 | nd = rb_prev(nd); | ||
1311 | } | ||
1312 | |||
1313 | return NULL; | ||
1314 | } | ||
1315 | |||
1316 | static void ui_browser__hists_seek(struct ui_browser *self, | ||
1317 | off_t offset, int whence) | ||
1318 | { | ||
1319 | struct hist_entry *h; | ||
1320 | struct rb_node *nd; | ||
1321 | bool first = true; | ||
1322 | |||
1323 | switch (whence) { | ||
1324 | case SEEK_SET: | ||
1325 | nd = hists__filter_entries(rb_first(self->entries)); | ||
1326 | break; | ||
1327 | case SEEK_CUR: | ||
1328 | nd = self->first_visible_entry; | ||
1329 | goto do_offset; | ||
1330 | case SEEK_END: | ||
1331 | nd = hists__filter_prev_entries(rb_last(self->entries)); | ||
1332 | first = false; | ||
1333 | break; | ||
1334 | default: | ||
1335 | return; | ||
1336 | } | ||
1337 | |||
1338 | /* | ||
1339 | * Moves not relative to the first visible entry invalidates its | ||
1340 | * row_offset: | ||
1341 | */ | ||
1342 | h = rb_entry(self->first_visible_entry, struct hist_entry, rb_node); | ||
1343 | h->row_offset = 0; | ||
1344 | |||
1345 | /* | ||
1346 | * Here we have to check if nd is expanded (+), if it is we can't go | ||
1347 | * the next top level hist_entry, instead we must compute an offset of | ||
1348 | * what _not_ to show and not change the first visible entry. | ||
1349 | * | ||
1350 | * This offset increments when we are going from top to bottom and | ||
1351 | * decreases when we're going from bottom to top. | ||
1352 | * | ||
1353 | * As we don't have backpointers to the top level in the callchains | ||
1354 | * structure, we need to always print the whole hist_entry callchain, | ||
1355 | * skipping the first ones that are before the first visible entry | ||
1356 | * and stop when we printed enough lines to fill the screen. | ||
1357 | */ | ||
1358 | do_offset: | ||
1359 | if (offset > 0) { | ||
1360 | do { | ||
1361 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
1362 | if (h->ms.unfolded) { | ||
1363 | u16 remaining = h->nr_rows - h->row_offset; | ||
1364 | if (offset > remaining) { | ||
1365 | offset -= remaining; | ||
1366 | h->row_offset = 0; | ||
1367 | } else { | ||
1368 | h->row_offset += offset; | ||
1369 | offset = 0; | ||
1370 | self->first_visible_entry = nd; | ||
1371 | break; | ||
1372 | } | ||
1373 | } | ||
1374 | nd = hists__filter_entries(rb_next(nd)); | ||
1375 | if (nd == NULL) | ||
1376 | break; | ||
1377 | --offset; | ||
1378 | self->first_visible_entry = nd; | ||
1379 | } while (offset != 0); | ||
1380 | } else if (offset < 0) { | ||
1381 | while (1) { | ||
1382 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
1383 | if (h->ms.unfolded) { | ||
1384 | if (first) { | ||
1385 | if (-offset > h->row_offset) { | ||
1386 | offset += h->row_offset; | ||
1387 | h->row_offset = 0; | ||
1388 | } else { | ||
1389 | h->row_offset += offset; | ||
1390 | offset = 0; | ||
1391 | self->first_visible_entry = nd; | ||
1392 | break; | ||
1393 | } | ||
1394 | } else { | ||
1395 | if (-offset > h->nr_rows) { | ||
1396 | offset += h->nr_rows; | ||
1397 | h->row_offset = 0; | ||
1398 | } else { | ||
1399 | h->row_offset = h->nr_rows + offset; | ||
1400 | offset = 0; | ||
1401 | self->first_visible_entry = nd; | ||
1402 | break; | ||
1403 | } | ||
1404 | } | ||
1405 | } | ||
1406 | |||
1407 | nd = hists__filter_prev_entries(rb_prev(nd)); | ||
1408 | if (nd == NULL) | ||
1409 | break; | ||
1410 | ++offset; | ||
1411 | self->first_visible_entry = nd; | ||
1412 | if (offset == 0) { | ||
1413 | /* | ||
1414 | * Last unfiltered hist_entry, check if it is | ||
1415 | * unfolded, if it is then we should have | ||
1416 | * row_offset at its last entry. | ||
1417 | */ | ||
1418 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
1419 | if (h->ms.unfolded) | ||
1420 | h->row_offset = h->nr_rows; | ||
1421 | break; | ||
1422 | } | ||
1423 | first = false; | ||
1424 | } | ||
1425 | } else { | ||
1426 | self->first_visible_entry = nd; | ||
1427 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
1428 | h->row_offset = 0; | ||
1429 | } | ||
1430 | } | ||
1431 | |||
1432 | static int callchain_node__count_rows_rb_tree(struct callchain_node *self) | ||
1433 | { | ||
1434 | int n = 0; | ||
1435 | struct rb_node *nd; | ||
1436 | |||
1437 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | ||
1438 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | ||
1439 | struct callchain_list *chain; | ||
1440 | char folded_sign = ' '; /* No children */ | ||
1441 | |||
1442 | list_for_each_entry(chain, &child->val, list) { | ||
1443 | ++n; | ||
1444 | /* We need this because we may not have children */ | ||
1445 | folded_sign = callchain_list__folded(chain); | ||
1446 | if (folded_sign == '+') | ||
1447 | break; | ||
1448 | } | ||
1449 | |||
1450 | if (folded_sign == '-') /* Have children and they're unfolded */ | ||
1451 | n += callchain_node__count_rows_rb_tree(child); | ||
1452 | } | ||
1453 | |||
1454 | return n; | ||
1455 | } | ||
1456 | |||
1457 | static int callchain_node__count_rows(struct callchain_node *node) | ||
1458 | { | ||
1459 | struct callchain_list *chain; | ||
1460 | bool unfolded = false; | ||
1461 | int n = 0; | ||
1462 | |||
1463 | list_for_each_entry(chain, &node->val, list) { | ||
1464 | ++n; | ||
1465 | unfolded = chain->ms.unfolded; | ||
1466 | } | ||
1467 | |||
1468 | if (unfolded) | ||
1469 | n += callchain_node__count_rows_rb_tree(node); | ||
1470 | |||
1471 | return n; | ||
1472 | } | ||
1473 | |||
1474 | static int callchain__count_rows(struct rb_root *chain) | ||
1475 | { | ||
1476 | struct rb_node *nd; | ||
1477 | int n = 0; | ||
1478 | |||
1479 | for (nd = rb_first(chain); nd; nd = rb_next(nd)) { | ||
1480 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
1481 | n += callchain_node__count_rows(node); | ||
1482 | } | ||
1483 | |||
1484 | return n; | ||
1485 | } | ||
1486 | |||
1487 | static bool hist_browser__toggle_fold(struct hist_browser *self) | ||
1488 | { | ||
1489 | if (map_symbol__toggle_fold(self->selection)) { | ||
1490 | struct hist_entry *he = self->he_selection; | ||
1491 | |||
1492 | hist_entry__init_have_children(he); | ||
1493 | self->hists->nr_entries -= he->nr_rows; | ||
1494 | |||
1495 | if (he->ms.unfolded) | ||
1496 | he->nr_rows = callchain__count_rows(&he->sorted_chain); | ||
1497 | else | ||
1498 | he->nr_rows = 0; | ||
1499 | self->hists->nr_entries += he->nr_rows; | ||
1500 | self->b.nr_entries = self->hists->nr_entries; | ||
1501 | |||
1502 | return true; | ||
1503 | } | ||
1504 | |||
1505 | /* If it doesn't have children, no toggling performed */ | ||
1506 | return false; | ||
1507 | } | ||
1508 | |||
1509 | static int hist_browser__run(struct hist_browser *self, const char *title, | ||
1510 | struct newtExitStruct *es) | ||
1511 | { | ||
1512 | char str[256], unit; | ||
1513 | unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE]; | ||
1514 | |||
1515 | self->b.entries = &self->hists->entries; | ||
1516 | self->b.nr_entries = self->hists->nr_entries; | ||
1517 | |||
1518 | hist_browser__refresh_dimensions(self); | ||
1519 | |||
1520 | nr_events = convert_unit(nr_events, &unit); | ||
1521 | snprintf(str, sizeof(str), "Events: %lu%c ", | ||
1522 | nr_events, unit); | ||
1523 | newtDrawRootText(0, 0, str); | ||
1524 | |||
1525 | if (ui_browser__show(&self->b, title) < 0) | ||
1526 | return -1; | ||
1527 | |||
1528 | newtFormAddHotKey(self->b.form, 'A'); | ||
1529 | newtFormAddHotKey(self->b.form, 'a'); | ||
1530 | newtFormAddHotKey(self->b.form, '?'); | ||
1531 | newtFormAddHotKey(self->b.form, 'h'); | ||
1532 | newtFormAddHotKey(self->b.form, 'H'); | ||
1533 | newtFormAddHotKey(self->b.form, 'd'); | ||
1534 | |||
1535 | newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); | ||
1536 | newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT); | ||
1537 | newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER); | ||
1538 | |||
1539 | while (1) { | ||
1540 | ui_browser__run(&self->b, es); | ||
1541 | |||
1542 | if (es->reason != NEWT_EXIT_HOTKEY) | ||
1543 | break; | ||
1544 | switch (es->u.key) { | ||
1545 | case 'd': { /* Debug */ | ||
1546 | static int seq; | ||
1547 | struct hist_entry *h = rb_entry(self->b.first_visible_entry, | ||
1548 | struct hist_entry, rb_node); | ||
1549 | ui_helpline__pop(); | ||
1550 | ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", | ||
1551 | seq++, self->b.nr_entries, | ||
1552 | self->hists->nr_entries, | ||
1553 | self->b.height, | ||
1554 | self->b.index, | ||
1555 | self->b.first_visible_entry_idx, | ||
1556 | h->row_offset, h->nr_rows); | ||
1557 | } | ||
1558 | continue; | ||
1559 | case NEWT_KEY_ENTER: | ||
1560 | if (hist_browser__toggle_fold(self)) | ||
1561 | break; | ||
1562 | /* fall thru */ | ||
1563 | default: | ||
1564 | return 0; | ||
1565 | } | ||
1566 | } | ||
1567 | return 0; | ||
1568 | } | ||