diff options
Diffstat (limited to 'tools/perf/ui/stdio/hist.c')
-rw-r--r-- | tools/perf/ui/stdio/hist.c | 302 |
1 files changed, 260 insertions, 42 deletions
diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index 387110d50b00..7aff5acf3265 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c | |||
@@ -165,8 +165,28 @@ static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root, | |||
165 | return ret; | 165 | return ret; |
166 | } | 166 | } |
167 | 167 | ||
168 | /* | ||
169 | * If have one single callchain root, don't bother printing | ||
170 | * its percentage (100 % in fractal mode and the same percentage | ||
171 | * than the hist in graph mode). This also avoid one level of column. | ||
172 | * | ||
173 | * However when percent-limit applied, it's possible that single callchain | ||
174 | * node have different (non-100% in fractal mode) percentage. | ||
175 | */ | ||
176 | static bool need_percent_display(struct rb_node *node, u64 parent_samples) | ||
177 | { | ||
178 | struct callchain_node *cnode; | ||
179 | |||
180 | if (rb_next(node)) | ||
181 | return true; | ||
182 | |||
183 | cnode = rb_entry(node, struct callchain_node, rb_node); | ||
184 | return callchain_cumul_hits(cnode) != parent_samples; | ||
185 | } | ||
186 | |||
168 | static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, | 187 | static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, |
169 | u64 total_samples, int left_margin) | 188 | u64 total_samples, u64 parent_samples, |
189 | int left_margin) | ||
170 | { | 190 | { |
171 | struct callchain_node *cnode; | 191 | struct callchain_node *cnode; |
172 | struct callchain_list *chain; | 192 | struct callchain_list *chain; |
@@ -177,13 +197,8 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, | |||
177 | int ret = 0; | 197 | int ret = 0; |
178 | char bf[1024]; | 198 | char bf[1024]; |
179 | 199 | ||
180 | /* | ||
181 | * If have one single callchain root, don't bother printing | ||
182 | * its percentage (100 % in fractal mode and the same percentage | ||
183 | * than the hist in graph mode). This also avoid one level of column. | ||
184 | */ | ||
185 | node = rb_first(root); | 200 | node = rb_first(root); |
186 | if (node && !rb_next(node)) { | 201 | if (node && !need_percent_display(node, parent_samples)) { |
187 | cnode = rb_entry(node, struct callchain_node, rb_node); | 202 | cnode = rb_entry(node, struct callchain_node, rb_node); |
188 | list_for_each_entry(chain, &cnode->val, list) { | 203 | list_for_each_entry(chain, &cnode->val, list) { |
189 | /* | 204 | /* |
@@ -213,9 +228,15 @@ static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root, | |||
213 | root = &cnode->rb_root; | 228 | root = &cnode->rb_root; |
214 | } | 229 | } |
215 | 230 | ||
231 | if (callchain_param.mode == CHAIN_GRAPH_REL) | ||
232 | total_samples = parent_samples; | ||
233 | |||
216 | ret += __callchain__fprintf_graph(fp, root, total_samples, | 234 | ret += __callchain__fprintf_graph(fp, root, total_samples, |
217 | 1, 1, left_margin); | 235 | 1, 1, left_margin); |
218 | ret += fprintf(fp, "\n"); | 236 | if (ret) { |
237 | /* do not add a blank line if it printed nothing */ | ||
238 | ret += fprintf(fp, "\n"); | ||
239 | } | ||
219 | 240 | ||
220 | return ret; | 241 | return ret; |
221 | } | 242 | } |
@@ -323,16 +344,19 @@ static size_t hist_entry_callchain__fprintf(struct hist_entry *he, | |||
323 | u64 total_samples, int left_margin, | 344 | u64 total_samples, int left_margin, |
324 | FILE *fp) | 345 | FILE *fp) |
325 | { | 346 | { |
347 | u64 parent_samples = he->stat.period; | ||
348 | |||
349 | if (symbol_conf.cumulate_callchain) | ||
350 | parent_samples = he->stat_acc->period; | ||
351 | |||
326 | switch (callchain_param.mode) { | 352 | switch (callchain_param.mode) { |
327 | case CHAIN_GRAPH_REL: | 353 | case CHAIN_GRAPH_REL: |
328 | return callchain__fprintf_graph(fp, &he->sorted_chain, | 354 | return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples, |
329 | symbol_conf.cumulate_callchain ? | 355 | parent_samples, left_margin); |
330 | he->stat_acc->period : he->stat.period, | ||
331 | left_margin); | ||
332 | break; | 356 | break; |
333 | case CHAIN_GRAPH_ABS: | 357 | case CHAIN_GRAPH_ABS: |
334 | return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples, | 358 | return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples, |
335 | left_margin); | 359 | parent_samples, left_margin); |
336 | break; | 360 | break; |
337 | case CHAIN_FLAT: | 361 | case CHAIN_FLAT: |
338 | return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples); | 362 | return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples); |
@@ -349,45 +373,66 @@ static size_t hist_entry_callchain__fprintf(struct hist_entry *he, | |||
349 | return 0; | 373 | return 0; |
350 | } | 374 | } |
351 | 375 | ||
352 | static size_t hist_entry__callchain_fprintf(struct hist_entry *he, | 376 | static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp) |
353 | struct hists *hists, | ||
354 | FILE *fp) | ||
355 | { | 377 | { |
356 | int left_margin = 0; | 378 | const char *sep = symbol_conf.field_sep; |
357 | u64 total_period = hists->stats.total_period; | 379 | struct perf_hpp_fmt *fmt; |
380 | char *start = hpp->buf; | ||
381 | int ret; | ||
382 | bool first = true; | ||
358 | 383 | ||
359 | if (field_order == NULL && (sort_order == NULL || | 384 | if (symbol_conf.exclude_other && !he->parent) |
360 | !prefixcmp(sort_order, "comm"))) { | 385 | return 0; |
361 | struct perf_hpp_fmt *fmt; | ||
362 | 386 | ||
363 | perf_hpp__for_each_format(fmt) { | 387 | hists__for_each_format(he->hists, fmt) { |
364 | if (!perf_hpp__is_sort_entry(fmt)) | 388 | if (perf_hpp__should_skip(fmt, he->hists)) |
365 | continue; | 389 | continue; |
366 | 390 | ||
367 | /* must be 'comm' sort entry */ | 391 | /* |
368 | left_margin = fmt->width(fmt, NULL, hists_to_evsel(hists)); | 392 | * If there's no field_sep, we still need |
369 | left_margin -= thread__comm_len(he->thread); | 393 | * to display initial ' '. |
370 | break; | 394 | */ |
371 | } | 395 | if (!sep || !first) { |
396 | ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " "); | ||
397 | advance_hpp(hpp, ret); | ||
398 | } else | ||
399 | first = false; | ||
400 | |||
401 | if (perf_hpp__use_color() && fmt->color) | ||
402 | ret = fmt->color(fmt, hpp, he); | ||
403 | else | ||
404 | ret = fmt->entry(fmt, hpp, he); | ||
405 | |||
406 | ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret); | ||
407 | advance_hpp(hpp, ret); | ||
372 | } | 408 | } |
373 | return hist_entry_callchain__fprintf(he, total_period, left_margin, fp); | 409 | |
410 | return hpp->buf - start; | ||
374 | } | 411 | } |
375 | 412 | ||
376 | static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp) | 413 | static int hist_entry__hierarchy_fprintf(struct hist_entry *he, |
414 | struct perf_hpp *hpp, | ||
415 | struct hists *hists, | ||
416 | FILE *fp) | ||
377 | { | 417 | { |
378 | const char *sep = symbol_conf.field_sep; | 418 | const char *sep = symbol_conf.field_sep; |
379 | struct perf_hpp_fmt *fmt; | 419 | struct perf_hpp_fmt *fmt; |
380 | char *start = hpp->buf; | 420 | struct perf_hpp_list_node *fmt_node; |
381 | int ret; | 421 | char *buf = hpp->buf; |
422 | size_t size = hpp->size; | ||
423 | int ret, printed = 0; | ||
382 | bool first = true; | 424 | bool first = true; |
383 | 425 | ||
384 | if (symbol_conf.exclude_other && !he->parent) | 426 | if (symbol_conf.exclude_other && !he->parent) |
385 | return 0; | 427 | return 0; |
386 | 428 | ||
387 | perf_hpp__for_each_format(fmt) { | 429 | ret = scnprintf(hpp->buf, hpp->size, "%*s", he->depth * HIERARCHY_INDENT, ""); |
388 | if (perf_hpp__should_skip(fmt, he->hists)) | 430 | advance_hpp(hpp, ret); |
389 | continue; | ||
390 | 431 | ||
432 | /* the first hpp_list_node is for overhead columns */ | ||
433 | fmt_node = list_first_entry(&hists->hpp_formats, | ||
434 | struct perf_hpp_list_node, list); | ||
435 | perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { | ||
391 | /* | 436 | /* |
392 | * If there's no field_sep, we still need | 437 | * If there's no field_sep, we still need |
393 | * to display initial ' '. | 438 | * to display initial ' '. |
@@ -403,10 +448,47 @@ static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp) | |||
403 | else | 448 | else |
404 | ret = fmt->entry(fmt, hpp, he); | 449 | ret = fmt->entry(fmt, hpp, he); |
405 | 450 | ||
451 | ret = hist_entry__snprintf_alignment(he, hpp, fmt, ret); | ||
406 | advance_hpp(hpp, ret); | 452 | advance_hpp(hpp, ret); |
407 | } | 453 | } |
408 | 454 | ||
409 | return hpp->buf - start; | 455 | if (!sep) |
456 | ret = scnprintf(hpp->buf, hpp->size, "%*s", | ||
457 | (hists->nr_hpp_node - 2) * HIERARCHY_INDENT, ""); | ||
458 | advance_hpp(hpp, ret); | ||
459 | |||
460 | printed += fprintf(fp, "%s", buf); | ||
461 | |||
462 | perf_hpp_list__for_each_format(he->hpp_list, fmt) { | ||
463 | hpp->buf = buf; | ||
464 | hpp->size = size; | ||
465 | |||
466 | /* | ||
467 | * No need to call hist_entry__snprintf_alignment() since this | ||
468 | * fmt is always the last column in the hierarchy mode. | ||
469 | */ | ||
470 | if (perf_hpp__use_color() && fmt->color) | ||
471 | fmt->color(fmt, hpp, he); | ||
472 | else | ||
473 | fmt->entry(fmt, hpp, he); | ||
474 | |||
475 | /* | ||
476 | * dynamic entries are right-aligned but we want left-aligned | ||
477 | * in the hierarchy mode | ||
478 | */ | ||
479 | printed += fprintf(fp, "%s%s", sep ?: " ", ltrim(buf)); | ||
480 | } | ||
481 | printed += putc('\n', fp); | ||
482 | |||
483 | if (symbol_conf.use_callchain && he->leaf) { | ||
484 | u64 total = hists__total_period(hists); | ||
485 | |||
486 | printed += hist_entry_callchain__fprintf(he, total, 0, fp); | ||
487 | goto out; | ||
488 | } | ||
489 | |||
490 | out: | ||
491 | return printed; | ||
410 | } | 492 | } |
411 | 493 | ||
412 | static int hist_entry__fprintf(struct hist_entry *he, size_t size, | 494 | static int hist_entry__fprintf(struct hist_entry *he, size_t size, |
@@ -418,24 +500,134 @@ static int hist_entry__fprintf(struct hist_entry *he, size_t size, | |||
418 | .buf = bf, | 500 | .buf = bf, |
419 | .size = size, | 501 | .size = size, |
420 | }; | 502 | }; |
503 | u64 total_period = hists->stats.total_period; | ||
421 | 504 | ||
422 | if (size == 0 || size > bfsz) | 505 | if (size == 0 || size > bfsz) |
423 | size = hpp.size = bfsz; | 506 | size = hpp.size = bfsz; |
424 | 507 | ||
508 | if (symbol_conf.report_hierarchy) | ||
509 | return hist_entry__hierarchy_fprintf(he, &hpp, hists, fp); | ||
510 | |||
425 | hist_entry__snprintf(he, &hpp); | 511 | hist_entry__snprintf(he, &hpp); |
426 | 512 | ||
427 | ret = fprintf(fp, "%s\n", bf); | 513 | ret = fprintf(fp, "%s\n", bf); |
428 | 514 | ||
429 | if (symbol_conf.use_callchain) | 515 | if (symbol_conf.use_callchain) |
430 | ret += hist_entry__callchain_fprintf(he, hists, fp); | 516 | ret += hist_entry_callchain__fprintf(he, total_period, 0, fp); |
431 | 517 | ||
432 | return ret; | 518 | return ret; |
433 | } | 519 | } |
434 | 520 | ||
521 | static int print_hierarchy_indent(const char *sep, int indent, | ||
522 | const char *line, FILE *fp) | ||
523 | { | ||
524 | if (sep != NULL || indent < 2) | ||
525 | return 0; | ||
526 | |||
527 | return fprintf(fp, "%-.*s", (indent - 2) * HIERARCHY_INDENT, line); | ||
528 | } | ||
529 | |||
530 | static int print_hierarchy_header(struct hists *hists, struct perf_hpp *hpp, | ||
531 | const char *sep, FILE *fp) | ||
532 | { | ||
533 | bool first_node, first_col; | ||
534 | int indent; | ||
535 | int depth; | ||
536 | unsigned width = 0; | ||
537 | unsigned header_width = 0; | ||
538 | struct perf_hpp_fmt *fmt; | ||
539 | struct perf_hpp_list_node *fmt_node; | ||
540 | |||
541 | indent = hists->nr_hpp_node; | ||
542 | |||
543 | /* preserve max indent depth for column headers */ | ||
544 | print_hierarchy_indent(sep, indent, spaces, fp); | ||
545 | |||
546 | /* the first hpp_list_node is for overhead columns */ | ||
547 | fmt_node = list_first_entry(&hists->hpp_formats, | ||
548 | struct perf_hpp_list_node, list); | ||
549 | |||
550 | perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { | ||
551 | fmt->header(fmt, hpp, hists_to_evsel(hists)); | ||
552 | fprintf(fp, "%s%s", hpp->buf, sep ?: " "); | ||
553 | } | ||
554 | |||
555 | /* combine sort headers with ' / ' */ | ||
556 | first_node = true; | ||
557 | list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { | ||
558 | if (!first_node) | ||
559 | header_width += fprintf(fp, " / "); | ||
560 | first_node = false; | ||
561 | |||
562 | first_col = true; | ||
563 | perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { | ||
564 | if (perf_hpp__should_skip(fmt, hists)) | ||
565 | continue; | ||
566 | |||
567 | if (!first_col) | ||
568 | header_width += fprintf(fp, "+"); | ||
569 | first_col = false; | ||
570 | |||
571 | fmt->header(fmt, hpp, hists_to_evsel(hists)); | ||
572 | rtrim(hpp->buf); | ||
573 | |||
574 | header_width += fprintf(fp, "%s", ltrim(hpp->buf)); | ||
575 | } | ||
576 | } | ||
577 | |||
578 | fprintf(fp, "\n# "); | ||
579 | |||
580 | /* preserve max indent depth for initial dots */ | ||
581 | print_hierarchy_indent(sep, indent, dots, fp); | ||
582 | |||
583 | /* the first hpp_list_node is for overhead columns */ | ||
584 | fmt_node = list_first_entry(&hists->hpp_formats, | ||
585 | struct perf_hpp_list_node, list); | ||
586 | |||
587 | first_col = true; | ||
588 | perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { | ||
589 | if (!first_col) | ||
590 | fprintf(fp, "%s", sep ?: ".."); | ||
591 | first_col = false; | ||
592 | |||
593 | width = fmt->width(fmt, hpp, hists_to_evsel(hists)); | ||
594 | fprintf(fp, "%.*s", width, dots); | ||
595 | } | ||
596 | |||
597 | depth = 0; | ||
598 | list_for_each_entry_continue(fmt_node, &hists->hpp_formats, list) { | ||
599 | first_col = true; | ||
600 | width = depth * HIERARCHY_INDENT; | ||
601 | |||
602 | perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) { | ||
603 | if (perf_hpp__should_skip(fmt, hists)) | ||
604 | continue; | ||
605 | |||
606 | if (!first_col) | ||
607 | width++; /* for '+' sign between column header */ | ||
608 | first_col = false; | ||
609 | |||
610 | width += fmt->width(fmt, hpp, hists_to_evsel(hists)); | ||
611 | } | ||
612 | |||
613 | if (width > header_width) | ||
614 | header_width = width; | ||
615 | |||
616 | depth++; | ||
617 | } | ||
618 | |||
619 | fprintf(fp, "%s%-.*s", sep ?: " ", header_width, dots); | ||
620 | |||
621 | fprintf(fp, "\n#\n"); | ||
622 | |||
623 | return 2; | ||
624 | } | ||
625 | |||
435 | size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, | 626 | size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, |
436 | int max_cols, float min_pcnt, FILE *fp) | 627 | int max_cols, float min_pcnt, FILE *fp) |
437 | { | 628 | { |
438 | struct perf_hpp_fmt *fmt; | 629 | struct perf_hpp_fmt *fmt; |
630 | struct perf_hpp_list_node *fmt_node; | ||
439 | struct rb_node *nd; | 631 | struct rb_node *nd; |
440 | size_t ret = 0; | 632 | size_t ret = 0; |
441 | unsigned int width; | 633 | unsigned int width; |
@@ -449,10 +641,11 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, | |||
449 | bool first = true; | 641 | bool first = true; |
450 | size_t linesz; | 642 | size_t linesz; |
451 | char *line = NULL; | 643 | char *line = NULL; |
644 | unsigned indent; | ||
452 | 645 | ||
453 | init_rem_hits(); | 646 | init_rem_hits(); |
454 | 647 | ||
455 | perf_hpp__for_each_format(fmt) | 648 | hists__for_each_format(hists, fmt) |
456 | perf_hpp__reset_width(fmt, hists); | 649 | perf_hpp__reset_width(fmt, hists); |
457 | 650 | ||
458 | if (symbol_conf.col_width_list_str) | 651 | if (symbol_conf.col_width_list_str) |
@@ -463,7 +656,16 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, | |||
463 | 656 | ||
464 | fprintf(fp, "# "); | 657 | fprintf(fp, "# "); |
465 | 658 | ||
466 | perf_hpp__for_each_format(fmt) { | 659 | if (symbol_conf.report_hierarchy) { |
660 | list_for_each_entry(fmt_node, &hists->hpp_formats, list) { | ||
661 | perf_hpp_list__for_each_format(&fmt_node->hpp, fmt) | ||
662 | perf_hpp__reset_width(fmt, hists); | ||
663 | } | ||
664 | nr_rows += print_hierarchy_header(hists, &dummy_hpp, sep, fp); | ||
665 | goto print_entries; | ||
666 | } | ||
667 | |||
668 | hists__for_each_format(hists, fmt) { | ||
467 | if (perf_hpp__should_skip(fmt, hists)) | 669 | if (perf_hpp__should_skip(fmt, hists)) |
468 | continue; | 670 | continue; |
469 | 671 | ||
@@ -487,7 +689,7 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, | |||
487 | 689 | ||
488 | fprintf(fp, "# "); | 690 | fprintf(fp, "# "); |
489 | 691 | ||
490 | perf_hpp__for_each_format(fmt) { | 692 | hists__for_each_format(hists, fmt) { |
491 | unsigned int i; | 693 | unsigned int i; |
492 | 694 | ||
493 | if (perf_hpp__should_skip(fmt, hists)) | 695 | if (perf_hpp__should_skip(fmt, hists)) |
@@ -520,7 +722,9 @@ print_entries: | |||
520 | goto out; | 722 | goto out; |
521 | } | 723 | } |
522 | 724 | ||
523 | for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { | 725 | indent = hists__overhead_width(hists) + 4; |
726 | |||
727 | for (nd = rb_first(&hists->entries); nd; nd = __rb_hierarchy_next(nd, HMD_FORCE_CHILD)) { | ||
524 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | 728 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); |
525 | float percent; | 729 | float percent; |
526 | 730 | ||
@@ -536,6 +740,20 @@ print_entries: | |||
536 | if (max_rows && ++nr_rows >= max_rows) | 740 | if (max_rows && ++nr_rows >= max_rows) |
537 | break; | 741 | break; |
538 | 742 | ||
743 | /* | ||
744 | * If all children are filtered out or percent-limited, | ||
745 | * display "no entry >= x.xx%" message. | ||
746 | */ | ||
747 | if (!h->leaf && !hist_entry__has_hierarchy_children(h, min_pcnt)) { | ||
748 | int depth = hists->nr_hpp_node + h->depth + 1; | ||
749 | |||
750 | print_hierarchy_indent(sep, depth, spaces, fp); | ||
751 | fprintf(fp, "%*sno entry >= %.2f%%\n", indent, "", min_pcnt); | ||
752 | |||
753 | if (max_rows && ++nr_rows >= max_rows) | ||
754 | break; | ||
755 | } | ||
756 | |||
539 | if (h->ms.map == NULL && verbose > 1) { | 757 | if (h->ms.map == NULL && verbose > 1) { |
540 | __map_groups__fprintf_maps(h->thread->mg, | 758 | __map_groups__fprintf_maps(h->thread->mg, |
541 | MAP__FUNCTION, fp); | 759 | MAP__FUNCTION, fp); |