Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf report: Implement browsing of individual samples

Now 'perf report' can show whole time periods with 'perf script', but
the user still has to find individual samples of interest manually.

It would be expensive and complicated to search for the right samples in
the whole perf file. Typically users only need to look at a small number
of samples for useful analysis.

Also the full scripts tend to show samples of all CPUs and all threads
mixed up, which can be very confusing on larger systems.

Add a new --samples option to save a small random number of samples per
hist entry.

Use a reservoir sample technique to select a representatve number of
samples.

Then allow browsing the samples using 'perf script' as part of the hist
entry context menu. This automatically adds the right filters, so only
the thread or cpu of the sample is displayed. Then we use less' search
functionality to directly jump the to the time stamp of the selected
sample.

It uses different menus for assembler and source display. Assembler
needs xed installed and source needs debuginfo.

Currently it only supports as many samples as fit on the screen due to
some limitations in the slang ui code.

Signed-off-by: Andi Kleen <ak@linux.intel.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Link: http://lkml.kernel.org/r/20190311174605.GA29294@tassilo.jf.intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

authored by

Andi Kleen and committed by
Arnaldo Carvalho de Melo
4968ac8f 6f3da20e

+226 -1
+6
tools/perf/Documentation/perf-config.txt
··· 584 584 llvm.opts:: 585 585 Options passed to llc. 586 586 587 + samples.*:: 588 + 589 + samples.context:: 590 + Define how many ns worth of time to show 591 + around samples in perf report sample context browser. 592 + 587 593 SEE ALSO 588 594 -------- 589 595 linkperf:perf[1]
+4
tools/perf/Documentation/perf-report.txt
··· 461 461 --socket-filter:: 462 462 Only report the samples on the processor socket that match with this filter 463 463 464 + --samples=N:: 465 + Save N individual samples for each histogram entry to show context in perf 466 + report tui browser. 467 + 464 468 --raw-trace:: 465 469 When displaying traceevent output, do not use print fmt or plugins. 466 470
+2
tools/perf/builtin-report.c
··· 1159 1159 OPT_BOOLEAN(0, "demangle-kernel", &symbol_conf.demangle_kernel, 1160 1160 "Enable kernel symbol demangling"), 1161 1161 OPT_BOOLEAN(0, "mem-mode", &report.mem_mode, "mem access profile"), 1162 + OPT_INTEGER(0, "samples", &symbol_conf.res_sample, 1163 + "Number of samples to save per histogram entry for individual browsing"), 1162 1164 OPT_CALLBACK(0, "percent-limit", &report, "percent", 1163 1165 "Don't show entries under that percent", parse_percent_limit), 1164 1166 OPT_CALLBACK(0, "percentage", NULL, "relative|absolute",
+1
tools/perf/ui/browsers/Build
··· 3 3 perf-y += map.o 4 4 perf-y += scripts.o 5 5 perf-y += header.o 6 + perf-y += res_sample.o 6 7 7 8 CFLAGS_annotate.o += -DENABLE_SLFUTURE_CONST 8 9 CFLAGS_hists.o += -DENABLE_SLFUTURE_CONST
+47
tools/perf/ui/browsers/hists.c
··· 1226 1226 hist_browser__hpp_color_overhead_guest_us; 1227 1227 perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color = 1228 1228 hist_browser__hpp_color_overhead_acc; 1229 + 1230 + res_sample_init(); 1229 1231 } 1230 1232 1231 1233 static int hist_browser__show_entry(struct hist_browser *browser, ··· 2347 2345 struct map_symbol ms; 2348 2346 int socket; 2349 2347 struct perf_evsel *evsel; 2348 + enum rstype rstype; 2350 2349 2351 2350 int (*fn)(struct hist_browser *browser, struct popup_action *act); 2352 2351 }; ··· 2576 2573 } 2577 2574 2578 2575 static int 2576 + do_res_sample_script(struct hist_browser *browser __maybe_unused, 2577 + struct popup_action *act) 2578 + { 2579 + struct hist_entry *he; 2580 + 2581 + he = hist_browser__selected_entry(browser); 2582 + res_sample_browse(he->res_samples, he->num_res, act->evsel, act->rstype); 2583 + return 0; 2584 + } 2585 + 2586 + static int 2579 2587 add_script_opt_2(struct hist_browser *browser __maybe_unused, 2580 2588 struct popup_action *act, char **optstr, 2581 2589 struct thread *thread, struct symbol *sym, ··· 2641 2627 act->time = he->time; 2642 2628 } 2643 2629 return n; 2630 + } 2631 + 2632 + static int 2633 + add_res_sample_opt(struct hist_browser *browser __maybe_unused, 2634 + struct popup_action *act, char **optstr, 2635 + struct res_sample *res_sample, 2636 + struct perf_evsel *evsel, 2637 + enum rstype type) 2638 + { 2639 + if (!res_sample) 2640 + return 0; 2641 + 2642 + if (asprintf(optstr, "Show context for individual samples %s", 2643 + type == A_ASM ? "with assembler" : 2644 + type == A_SOURCE ? "with source" : "") < 0) 2645 + return 0; 2646 + 2647 + act->fn = do_res_sample_script; 2648 + act->evsel = evsel; 2649 + act->rstype = type; 2650 + return 1; 2644 2651 } 2645 2652 2646 2653 static int ··· 3150 3115 } 3151 3116 nr_options += add_script_opt(browser, &actions[nr_options], 3152 3117 &options[nr_options], NULL, NULL, evsel); 3118 + nr_options += add_res_sample_opt(browser, &actions[nr_options], 3119 + &options[nr_options], 3120 + hist_browser__selected_entry(browser)->res_samples, 3121 + evsel, A_NORMAL); 3122 + nr_options += add_res_sample_opt(browser, &actions[nr_options], 3123 + &options[nr_options], 3124 + hist_browser__selected_entry(browser)->res_samples, 3125 + evsel, A_ASM); 3126 + nr_options += add_res_sample_opt(browser, &actions[nr_options], 3127 + &options[nr_options], 3128 + hist_browser__selected_entry(browser)->res_samples, 3129 + evsel, A_SOURCE); 3153 3130 nr_options += add_switch_opt(browser, &actions[nr_options], 3154 3131 &options[nr_options]); 3155 3132 skip_scripting:
+94
tools/perf/ui/browsers/res_sample.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Display a menu with individual samples to browse with perf script */ 3 + #include "util.h" 4 + #include "hist.h" 5 + #include "evsel.h" 6 + #include "hists.h" 7 + #include "sort.h" 8 + #include "config.h" 9 + #include "time-utils.h" 10 + #include <linux/time64.h> 11 + 12 + static u64 context_len = 10 * NSEC_PER_MSEC; 13 + 14 + static int res_sample_config(const char *var, const char *value, void *data __maybe_unused) 15 + { 16 + if (!strcmp(var, "samples.context")) 17 + return perf_config_u64(&context_len, var, value); 18 + return 0; 19 + } 20 + 21 + void res_sample_init(void) 22 + { 23 + perf_config(res_sample_config, NULL); 24 + } 25 + 26 + int res_sample_browse(struct res_sample *res_samples, int num_res, 27 + struct perf_evsel *evsel, enum rstype rstype) 28 + { 29 + char **names; 30 + int i, n; 31 + int choice; 32 + char *cmd; 33 + char pbuf[256], tidbuf[32], cpubuf[32]; 34 + const char *perf = perf_exe(pbuf, sizeof pbuf); 35 + char trange[128], tsample[64]; 36 + struct res_sample *r; 37 + char extra_format[256]; 38 + 39 + /* For now since ui__popup_menu doesn't like lists that don't fit */ 40 + num_res = max(min(SLtt_Screen_Rows - 4, num_res), 0); 41 + 42 + names = calloc(num_res, sizeof(char *)); 43 + if (!names) 44 + return -1; 45 + for (i = 0; i < num_res; i++) { 46 + char tbuf[64]; 47 + 48 + timestamp__scnprintf_nsec(res_samples[i].time, tbuf, sizeof tbuf); 49 + if (asprintf(&names[i], "%s: CPU %d tid %d", tbuf, 50 + res_samples[i].cpu, res_samples[i].tid) < 0) { 51 + while (--i >= 0) 52 + free(names[i]); 53 + free(names); 54 + return -1; 55 + } 56 + } 57 + choice = ui__popup_menu(num_res, names); 58 + for (i = 0; i < num_res; i++) 59 + free(names[i]); 60 + free(names); 61 + 62 + if (choice < 0 || choice >= num_res) 63 + return -1; 64 + r = &res_samples[choice]; 65 + 66 + n = timestamp__scnprintf_nsec(r->time - context_len, trange, sizeof trange); 67 + trange[n++] = ','; 68 + timestamp__scnprintf_nsec(r->time + context_len, trange + n, sizeof trange - n); 69 + 70 + timestamp__scnprintf_nsec(r->time, tsample, sizeof tsample); 71 + 72 + attr_to_script(extra_format, &evsel->attr); 73 + 74 + if (asprintf(&cmd, "%s script %s%s --time %s %s%s %s%s --ns %s %s %s %s %s | less +/%s", 75 + perf, 76 + input_name ? "-i " : "", 77 + input_name ? input_name : "", 78 + trange, 79 + r->cpu >= 0 ? "--cpu " : "", 80 + r->cpu >= 0 ? (sprintf(cpubuf, "%d", r->cpu), cpubuf) : "", 81 + r->tid ? "--tid " : "", 82 + r->tid ? (sprintf(tidbuf, "%d", r->tid), tidbuf) : "", 83 + extra_format, 84 + rstype == A_ASM ? "-F +insn --xed" : 85 + rstype == A_SOURCE ? "-F +srcline,+srccode" : "", 86 + symbol_conf.inline_name ? "--inline" : "", 87 + "--show-lost-events ", 88 + r->tid ? "--show-switch-events --show-task-events " : "", 89 + tsample) < 0) 90 + return -1; 91 + run_script(cmd); 92 + free(cmd); 93 + return 0; 94 + }
+1 -1
tools/perf/ui/browsers/scripts.c
··· 125 125 return ret; 126 126 } 127 127 128 - static void run_script(char *cmd) 128 + void run_script(char *cmd) 129 129 { 130 130 pr_debug("Running %s\n", cmd); 131 131 SLang_reset_tty();
+39
tools/perf/util/hist.c
··· 436 436 goto err_rawdata; 437 437 } 438 438 439 + if (symbol_conf.res_sample) { 440 + he->res_samples = calloc(sizeof(struct res_sample), 441 + symbol_conf.res_sample); 442 + if (!he->res_samples) 443 + goto err_srcline; 444 + } 445 + 439 446 INIT_LIST_HEAD(&he->pairs.node); 440 447 thread__get(he->thread); 441 448 he->hroot_in = RB_ROOT_CACHED; ··· 452 445 he->leaf = true; 453 446 454 447 return 0; 448 + 449 + err_srcline: 450 + free(he->srcline); 455 451 456 452 err_rawdata: 457 453 free(he->raw_data); ··· 613 603 return he; 614 604 } 615 605 606 + static unsigned random_max(unsigned high) 607 + { 608 + unsigned thresh = -high % high; 609 + for (;;) { 610 + unsigned r = random(); 611 + if (r >= thresh) 612 + return r % high; 613 + } 614 + } 615 + 616 + static void hists__res_sample(struct hist_entry *he, struct perf_sample *sample) 617 + { 618 + struct res_sample *r; 619 + int j; 620 + 621 + if (he->num_res < symbol_conf.res_sample) { 622 + j = he->num_res++; 623 + } else { 624 + j = random_max(symbol_conf.res_sample); 625 + } 626 + r = &he->res_samples[j]; 627 + r->time = sample->time; 628 + r->cpu = sample->cpu; 629 + r->tid = sample->tid; 630 + } 631 + 616 632 static struct hist_entry* 617 633 __hists__add_entry(struct hists *hists, 618 634 struct addr_location *al, ··· 686 650 687 651 if (!hists->has_callchains && he && he->callchain_size != 0) 688 652 hists->has_callchains = true; 653 + if (he && symbol_conf.res_sample) 654 + hists__res_sample(he, sample); 689 655 return he; 690 656 } 691 657 ··· 1211 1173 mem_info__zput(he->mem_info); 1212 1174 } 1213 1175 1176 + zfree(&he->res_samples); 1214 1177 zfree(&he->stat_acc); 1215 1178 free_srcline(he->srcline); 1216 1179 if (he->srcfile && he->srcfile[0])
+22
tools/perf/util/hist.h
··· 433 433 }; 434 434 435 435 struct annotation_options; 436 + struct res_sample; 437 + 438 + enum rstype { 439 + A_NORMAL, 440 + A_ASM, 441 + A_SOURCE 442 + }; 436 443 437 444 #ifdef HAVE_SLANG_SUPPORT 438 445 #include "../ui/keysyms.h" ··· 461 454 struct annotation_options *annotation_options); 462 455 463 456 int script_browse(const char *script_opt, struct perf_evsel *evsel); 457 + 458 + void run_script(char *cmd); 459 + int res_sample_browse(struct res_sample *res_samples, int num_res, 460 + struct perf_evsel *evsel, enum rstype rstype); 461 + void res_sample_init(void); 464 462 #else 465 463 static inline 466 464 int perf_evlist__tui_browse_hists(struct perf_evlist *evlist __maybe_unused, ··· 499 487 { 500 488 return 0; 501 489 } 490 + 491 + static inline int res_sample_browse(struct res_sample *res_samples __maybe_unused, 492 + int num_res __maybe_unused, 493 + struct perf_evsel *evsel __maybe_unused, 494 + enum rstype rstype __maybe_unused) 495 + { 496 + return 0; 497 + } 498 + 499 + static inline void res_sample_init(void) {} 502 500 503 501 #define K_LEFT -1000 504 502 #define K_RIGHT -2000
+8
tools/perf/util/sort.h
··· 47 47 extern enum sort_type sort__first_dimension; 48 48 extern const char default_mem_sort_order[]; 49 49 50 + struct res_sample { 51 + u64 time; 52 + int cpu; 53 + int tid; 54 + }; 55 + 50 56 struct he_stat { 51 57 u64 period; 52 58 u64 period_sys; ··· 146 140 struct mem_info *mem_info; 147 141 void *raw_data; 148 142 u32 raw_size; 143 + int num_res; 144 + struct res_sample *res_samples; 149 145 void *trace_output; 150 146 struct perf_hpp_list *hpp_list; 151 147 struct hist_entry *parent_he;
+1
tools/perf/util/symbol.c
··· 51 51 .symfs = "", 52 52 .event_group = true, 53 53 .inline_name = true, 54 + .res_sample = 0, 54 55 }; 55 56 56 57 static enum dso_binary_type binary_type_symtab[] = {
+1
tools/perf/util/symbol_conf.h
··· 68 68 struct intlist *pid_list, 69 69 *tid_list; 70 70 const char *symfs; 71 + int res_sample; 71 72 }; 72 73 73 74 extern struct symbol_conf symbol_conf;