diff options
Diffstat (limited to 'tools/perf')
| -rw-r--r-- | tools/perf/builtin-test.c | 919 | ||||
| -rw-r--r-- | tools/perf/util/include/linux/module.h | 6 | ||||
| -rw-r--r-- | tools/perf/util/ui/browser.c | 356 | ||||
| -rw-r--r-- | tools/perf/util/ui/browser.h | 52 | ||||
| -rw-r--r-- | tools/perf/util/ui/browsers/annotate.c | 302 | ||||
| -rw-r--r-- | tools/perf/util/ui/browsers/hists.c | 1138 | ||||
| -rw-r--r-- | tools/perf/util/ui/browsers/map.c | 156 | ||||
| -rw-r--r-- | tools/perf/util/ui/browsers/map.h | 6 | ||||
| -rw-r--r-- | tools/perf/util/ui/browsers/top.c | 212 | ||||
| -rw-r--r-- | tools/perf/util/ui/helpline.c | 72 | ||||
| -rw-r--r-- | tools/perf/util/ui/helpline.h | 11 | ||||
| -rw-r--r-- | tools/perf/util/ui/libslang.h | 27 | ||||
| -rw-r--r-- | tools/perf/util/ui/progress.c | 60 | ||||
| -rw-r--r-- | tools/perf/util/ui/progress.h | 11 | ||||
| -rw-r--r-- | tools/perf/util/ui/setup.c | 46 | ||||
| -rw-r--r-- | tools/perf/util/ui/ui.h | 8 | ||||
| -rw-r--r-- | tools/perf/util/ui/util.c | 130 | ||||
| -rw-r--r-- | tools/perf/util/ui/util.h | 10 |
18 files changed, 3522 insertions, 0 deletions
diff --git a/tools/perf/builtin-test.c b/tools/perf/builtin-test.c new file mode 100644 index 00000000000..efe696f936e --- /dev/null +++ b/tools/perf/builtin-test.c | |||
| @@ -0,0 +1,919 @@ | |||
| 1 | /* | ||
| 2 | * builtin-test.c | ||
| 3 | * | ||
| 4 | * Builtin regression testing command: ever growing number of sanity tests | ||
| 5 | */ | ||
| 6 | #include "builtin.h" | ||
| 7 | |||
| 8 | #include "util/cache.h" | ||
| 9 | #include "util/debug.h" | ||
| 10 | #include "util/evlist.h" | ||
| 11 | #include "util/parse-options.h" | ||
| 12 | #include "util/parse-events.h" | ||
| 13 | #include "util/symbol.h" | ||
| 14 | #include "util/thread_map.h" | ||
| 15 | #include "../../include/linux/hw_breakpoint.h" | ||
| 16 | |||
| 17 | static long page_size; | ||
| 18 | |||
| 19 | static int vmlinux_matches_kallsyms_filter(struct map *map __used, struct symbol *sym) | ||
| 20 | { | ||
| 21 | bool *visited = symbol__priv(sym); | ||
| 22 | *visited = true; | ||
| 23 | return 0; | ||
| 24 | } | ||
| 25 | |||
| 26 | static int test__vmlinux_matches_kallsyms(void) | ||
| 27 | { | ||
| 28 | int err = -1; | ||
| 29 | struct rb_node *nd; | ||
| 30 | struct symbol *sym; | ||
| 31 | struct map *kallsyms_map, *vmlinux_map; | ||
| 32 | struct machine kallsyms, vmlinux; | ||
| 33 | enum map_type type = MAP__FUNCTION; | ||
| 34 | struct ref_reloc_sym ref_reloc_sym = { .name = "_stext", }; | ||
| 35 | |||
| 36 | /* | ||
| 37 | * Step 1: | ||
| 38 | * | ||
| 39 | * Init the machines that will hold kernel, modules obtained from | ||
| 40 | * both vmlinux + .ko files and from /proc/kallsyms split by modules. | ||
| 41 | */ | ||
| 42 | machine__init(&kallsyms, "", HOST_KERNEL_ID); | ||
| 43 | machine__init(&vmlinux, "", HOST_KERNEL_ID); | ||
| 44 | |||
| 45 | /* | ||
| 46 | * Step 2: | ||
| 47 | * | ||
| 48 | * Create the kernel maps for kallsyms and the DSO where we will then | ||
| 49 | * load /proc/kallsyms. Also create the modules maps from /proc/modules | ||
| 50 | * and find the .ko files that match them in /lib/modules/`uname -r`/. | ||
| 51 | */ | ||
| 52 | if (machine__create_kernel_maps(&kallsyms) < 0) { | ||
| 53 | pr_debug("machine__create_kernel_maps "); | ||
| 54 | return -1; | ||
| 55 | } | ||
| 56 | |||
| 57 | /* | ||
| 58 | * Step 3: | ||
| 59 | * | ||
| 60 | * Load and split /proc/kallsyms into multiple maps, one per module. | ||
| 61 | */ | ||
| 62 | if (machine__load_kallsyms(&kallsyms, "/proc/kallsyms", type, NULL) <= 0) { | ||
| 63 | pr_debug("dso__load_kallsyms "); | ||
| 64 | goto out; | ||
| 65 | } | ||
| 66 | |||
| 67 | /* | ||
| 68 | * Step 4: | ||
| 69 | * | ||
| 70 | * kallsyms will be internally on demand sorted by name so that we can | ||
| 71 | * find the reference relocation * symbol, i.e. the symbol we will use | ||
| 72 | * to see if the running kernel was relocated by checking if it has the | ||
| 73 | * same value in the vmlinux file we load. | ||
| 74 | */ | ||
| 75 | kallsyms_map = machine__kernel_map(&kallsyms, type); | ||
| 76 | |||
| 77 | sym = map__find_symbol_by_name(kallsyms_map, ref_reloc_sym.name, NULL); | ||
| 78 | if (sym == NULL) { | ||
| 79 | pr_debug("dso__find_symbol_by_name "); | ||
| 80 | goto out; | ||
| 81 | } | ||
| 82 | |||
| 83 | ref_reloc_sym.addr = sym->start; | ||
| 84 | |||
| 85 | /* | ||
| 86 | * Step 5: | ||
| 87 | * | ||
| 88 | * Now repeat step 2, this time for the vmlinux file we'll auto-locate. | ||
| 89 | */ | ||
| 90 | if (machine__create_kernel_maps(&vmlinux) < 0) { | ||
| 91 | pr_debug("machine__create_kernel_maps "); | ||
| 92 | goto out; | ||
| 93 | } | ||
| 94 | |||
| 95 | vmlinux_map = machine__kernel_map(&vmlinux, type); | ||
| 96 | map__kmap(vmlinux_map)->ref_reloc_sym = &ref_reloc_sym; | ||
| 97 | |||
| 98 | /* | ||
| 99 | * Step 6: | ||
| 100 | * | ||
| 101 | * Locate a vmlinux file in the vmlinux path that has a buildid that | ||
| 102 | * matches the one of the running kernel. | ||
| 103 | * | ||
| 104 | * While doing that look if we find the ref reloc symbol, if we find it | ||
| 105 | * we'll have its ref_reloc_symbol.unrelocated_addr and then | ||
| 106 | * maps__reloc_vmlinux will notice and set proper ->[un]map_ip routines | ||
| 107 | * to fixup the symbols. | ||
| 108 | */ | ||
| 109 | if (machine__load_vmlinux_path(&vmlinux, type, | ||
| 110 | vmlinux_matches_kallsyms_filter) <= 0) { | ||
| 111 | pr_debug("machine__load_vmlinux_path "); | ||
| 112 | goto out; | ||
| 113 | } | ||
| 114 | |||
| 115 | err = 0; | ||
| 116 | /* | ||
| 117 | * Step 7: | ||
| 118 | * | ||
| 119 | * Now look at the symbols in the vmlinux DSO and check if we find all of them | ||
| 120 | * in the kallsyms dso. For the ones that are in both, check its names and | ||
| 121 | * end addresses too. | ||
| 122 | */ | ||
| 123 | for (nd = rb_first(&vmlinux_map->dso->symbols[type]); nd; nd = rb_next(nd)) { | ||
| 124 | struct symbol *pair, *first_pair; | ||
| 125 | bool backwards = true; | ||
| 126 | |||
| 127 | sym = rb_entry(nd, struct symbol, rb_node); | ||
| 128 | |||
| 129 | if (sym->start == sym->end) | ||
| 130 | continue; | ||
| 131 | |||
| 132 | first_pair = machine__find_kernel_symbol(&kallsyms, type, sym->start, NULL, NULL); | ||
| 133 | pair = first_pair; | ||
| 134 | |||
| 135 | if (pair && pair->start == sym->start) { | ||
| 136 | next_pair: | ||
| 137 | if (strcmp(sym->name, pair->name) == 0) { | ||
| 138 | /* | ||
| 139 | * kallsyms don't have the symbol end, so we | ||
| 140 | * set that by using the next symbol start - 1, | ||
| 141 | * in some cases we get this up to a page | ||
| 142 | * wrong, trace_kmalloc when I was developing | ||
| 143 | * this code was one such example, 2106 bytes | ||
| 144 | * off the real size. More than that and we | ||
| 145 | * _really_ have a problem. | ||
| 146 | */ | ||
| 147 | s64 skew = sym->end - pair->end; | ||
| 148 | if (llabs(skew) < page_size) | ||
| 149 | continue; | ||
| 150 | |||
| 151 | pr_debug("%#" PRIx64 ": diff end addr for %s v: %#" PRIx64 " k: %#" PRIx64 "\n", | ||
| 152 | sym->start, sym->name, sym->end, pair->end); | ||
| 153 | } else { | ||
| 154 | struct rb_node *nnd; | ||
| 155 | detour: | ||
| 156 | nnd = backwards ? rb_prev(&pair->rb_node) : | ||
| 157 | rb_next(&pair->rb_node); | ||
| 158 | if (nnd) { | ||
| 159 | struct symbol *next = rb_entry(nnd, struct symbol, rb_node); | ||
| 160 | |||
| 161 | if (next->start == sym->start) { | ||
| 162 | pair = next; | ||
| 163 | goto next_pair; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | if (backwards) { | ||
| 168 | backwards = false; | ||
| 169 | pair = first_pair; | ||
| 170 | goto detour; | ||
| 171 | } | ||
| 172 | |||
| 173 | pr_debug("%#" PRIx64 ": diff name v: %s k: %s\n", | ||
| 174 | sym->start, sym->name, pair->name); | ||
| 175 | } | ||
| 176 | } else | ||
| 177 | pr_debug("%#" PRIx64 ": %s not on kallsyms\n", sym->start, sym->name); | ||
| 178 | |||
| 179 | err = -1; | ||
| 180 | } | ||
| 181 | |||
| 182 | if (!verbose) | ||
| 183 | goto out; | ||
| 184 | |||
| 185 | pr_info("Maps only in vmlinux:\n"); | ||
| 186 | |||
| 187 | for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { | ||
| 188 | struct map *pos = rb_entry(nd, struct map, rb_node), *pair; | ||
| 189 | /* | ||
| 190 | * If it is the kernel, kallsyms is always "[kernel.kallsyms]", while | ||
| 191 | * the kernel will have the path for the vmlinux file being used, | ||
| 192 | * so use the short name, less descriptive but the same ("[kernel]" in | ||
| 193 | * both cases. | ||
| 194 | */ | ||
| 195 | pair = map_groups__find_by_name(&kallsyms.kmaps, type, | ||
| 196 | (pos->dso->kernel ? | ||
| 197 | pos->dso->short_name : | ||
| 198 | pos->dso->name)); | ||
| 199 | if (pair) | ||
| 200 | pair->priv = 1; | ||
| 201 | else | ||
| 202 | map__fprintf(pos, stderr); | ||
| 203 | } | ||
| 204 | |||
| 205 | pr_info("Maps in vmlinux with a different name in kallsyms:\n"); | ||
| 206 | |||
| 207 | for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { | ||
| 208 | struct map *pos = rb_entry(nd, struct map, rb_node), *pair; | ||
| 209 | |||
| 210 | pair = map_groups__find(&kallsyms.kmaps, type, pos->start); | ||
| 211 | if (pair == NULL || pair->priv) | ||
| 212 | continue; | ||
| 213 | |||
| 214 | if (pair->start == pos->start) { | ||
| 215 | pair->priv = 1; | ||
| 216 | pr_info(" %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as", | ||
| 217 | pos->start, pos->end, pos->pgoff, pos->dso->name); | ||
| 218 | if (pos->pgoff != pair->pgoff || pos->end != pair->end) | ||
| 219 | pr_info(": \n*%" PRIx64 "-%" PRIx64 " %" PRIx64 "", | ||
| 220 | pair->start, pair->end, pair->pgoff); | ||
| 221 | pr_info(" %s\n", pair->dso->name); | ||
| 222 | pair->priv = 1; | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | pr_info("Maps only in kallsyms:\n"); | ||
| 227 | |||
| 228 | for (nd = rb_first(&kallsyms.kmaps.maps[type]); | ||
| 229 | nd; nd = rb_next(nd)) { | ||
| 230 | struct map *pos = rb_entry(nd, struct map, rb_node); | ||
| 231 | |||
| 232 | if (!pos->priv) | ||
| 233 | map__fprintf(pos, stderr); | ||
| 234 | } | ||
| 235 | out: | ||
| 236 | return err; | ||
| 237 | } | ||
| 238 | |||
| 239 | #include "util/cpumap.h" | ||
| 240 | #include "util/evsel.h" | ||
| 241 | #include <sys/types.h> | ||
| 242 | |||
| 243 | static int trace_event__id(const char *evname) | ||
| 244 | { | ||
| 245 | char *filename; | ||
| 246 | int err = -1, fd; | ||
| 247 | |||
| 248 | if (asprintf(&filename, | ||
| 249 | "%s/syscalls/%s/id", | ||
| 250 | debugfs_path, evname) < 0) | ||
| 251 | return -1; | ||
| 252 | |||
| 253 | fd = open(filename, O_RDONLY); | ||
| 254 | if (fd >= 0) { | ||
| 255 | char id[16]; | ||
| 256 | if (read(fd, id, sizeof(id)) > 0) | ||
| 257 | err = atoi(id); | ||
| 258 | close(fd); | ||
| 259 | } | ||
| 260 | |||
| 261 | free(filename); | ||
| 262 | return err; | ||
| 263 | } | ||
| 264 | |||
| 265 | static int test__open_syscall_event(void) | ||
| 266 | { | ||
| 267 | int err = -1, fd; | ||
| 268 | struct thread_map *threads; | ||
| 269 | struct perf_evsel *evsel; | ||
| 270 | struct perf_event_attr attr; | ||
| 271 | unsigned int nr_open_calls = 111, i; | ||
| 272 | int id = trace_event__id("sys_enter_open"); | ||
| 273 | |||
| 274 | if (id < 0) { | ||
| 275 | pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); | ||
| 276 | return -1; | ||
| 277 | } | ||
| 278 | |||
| 279 | threads = thread_map__new(-1, getpid()); | ||
| 280 | if (threads == NULL) { | ||
| 281 | pr_debug("thread_map__new\n"); | ||
| 282 | return -1; | ||
| 283 | } | ||
| 284 | |||
| 285 | memset(&attr, 0, sizeof(attr)); | ||
| 286 | attr.type = PERF_TYPE_TRACEPOINT; | ||
| 287 | attr.config = id; | ||
| 288 | evsel = perf_evsel__new(&attr, 0); | ||
| 289 | if (evsel == NULL) { | ||
| 290 | pr_debug("perf_evsel__new\n"); | ||
| 291 | goto out_thread_map_delete; | ||
| 292 | } | ||
| 293 | |||
| 294 | if (perf_evsel__open_per_thread(evsel, threads, false) < 0) { | ||
| 295 | pr_debug("failed to open counter: %s, " | ||
| 296 | "tweak /proc/sys/kernel/perf_event_paranoid?\n", | ||
| 297 | strerror(errno)); | ||
| 298 | goto out_evsel_delete; | ||
| 299 | } | ||
| 300 | |||
| 301 | for (i = 0; i < nr_open_calls; ++i) { | ||
| 302 | fd = open("/etc/passwd", O_RDONLY); | ||
| 303 | close(fd); | ||
| 304 | } | ||
| 305 | |||
| 306 | if (perf_evsel__read_on_cpu(evsel, 0, 0) < 0) { | ||
| 307 | pr_debug("perf_evsel__read_on_cpu\n"); | ||
| 308 | goto out_close_fd; | ||
| 309 | } | ||
| 310 | |||
| 311 | if (evsel->counts->cpu[0].val != nr_open_calls) { | ||
| 312 | pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls, got %" PRIu64 "\n", | ||
| 313 | nr_open_calls, evsel->counts->cpu[0].val); | ||
| 314 | goto out_close_fd; | ||
| 315 | } | ||
| 316 | |||
| 317 | err = 0; | ||
| 318 | out_close_fd: | ||
| 319 | perf_evsel__close_fd(evsel, 1, threads->nr); | ||
| 320 | out_evsel_delete: | ||
| 321 | perf_evsel__delete(evsel); | ||
| 322 | out_thread_map_delete: | ||
| 323 | thread_map__delete(threads); | ||
| 324 | return err; | ||
| 325 | } | ||
| 326 | |||
| 327 | #include <sched.h> | ||
| 328 | |||
| 329 | static int test__open_syscall_event_on_all_cpus(void) | ||
| 330 | { | ||
| 331 | int err = -1, fd, cpu; | ||
| 332 | struct thread_map *threads; | ||
| 333 | struct cpu_map *cpus; | ||
| 334 | struct perf_evsel *evsel; | ||
| 335 | struct perf_event_attr attr; | ||
| 336 | unsigned int nr_open_calls = 111, i; | ||
| 337 | cpu_set_t cpu_set; | ||
| 338 | int id = trace_event__id("sys_enter_open"); | ||
| 339 | |||
| 340 | if (id < 0) { | ||
| 341 | pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); | ||
| 342 | return -1; | ||
| 343 | } | ||
| 344 | |||
| 345 | threads = thread_map__new(-1, getpid()); | ||
| 346 | if (threads == NULL) { | ||
| 347 | pr_debug("thread_map__new\n"); | ||
| 348 | return -1; | ||
| 349 | } | ||
| 350 | |||
| 351 | cpus = cpu_map__new(NULL); | ||
| 352 | if (cpus == NULL) { | ||
| 353 | pr_debug("cpu_map__new\n"); | ||
| 354 | goto out_thread_map_delete; | ||
| 355 | } | ||
| 356 | |||
| 357 | |||
| 358 | CPU_ZERO(&cpu_set); | ||
| 359 | |||
| 360 | memset(&attr, 0, sizeof(attr)); | ||
| 361 | attr.type = PERF_TYPE_TRACEPOINT; | ||
| 362 | attr.config = id; | ||
| 363 | evsel = perf_evsel__new(&attr, 0); | ||
| 364 | if (evsel == NULL) { | ||
| 365 | pr_debug("perf_evsel__new\n"); | ||
| 366 | goto out_thread_map_delete; | ||
| 367 | } | ||
| 368 | |||
| 369 | if (perf_evsel__open(evsel, cpus, threads, false) < 0) { | ||
| 370 | pr_debug("failed to open counter: %s, " | ||
| 371 | "tweak /proc/sys/kernel/perf_event_paranoid?\n", | ||
| 372 | strerror(errno)); | ||
| 373 | goto out_evsel_delete; | ||
| 374 | } | ||
| 375 | |||
| 376 | for (cpu = 0; cpu < cpus->nr; ++cpu) { | ||
| 377 | unsigned int ncalls = nr_open_calls + cpu; | ||
| 378 | /* | ||
| 379 | * XXX eventually lift this restriction in a way that | ||
| 380 | * keeps perf building on older glibc installations | ||
| 381 | * without CPU_ALLOC. 1024 cpus in 2010 still seems | ||
| 382 | * a reasonable upper limit tho :-) | ||
| 383 | */ | ||
| 384 | if (cpus->map[cpu] >= CPU_SETSIZE) { | ||
| 385 | pr_debug("Ignoring CPU %d\n", cpus->map[cpu]); | ||
| 386 | continue; | ||
| 387 | } | ||
| 388 | |||
| 389 | CPU_SET(cpus->map[cpu], &cpu_set); | ||
| 390 | if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { | ||
| 391 | pr_debug("sched_setaffinity() failed on CPU %d: %s ", | ||
| 392 | cpus->map[cpu], | ||
| 393 | strerror(errno)); | ||
| 394 | goto out_close_fd; | ||
| 395 | } | ||
| 396 | for (i = 0; i < ncalls; ++i) { | ||
| 397 | fd = open("/etc/passwd", O_RDONLY); | ||
| 398 | close(fd); | ||
| 399 | } | ||
| 400 | CPU_CLR(cpus->map[cpu], &cpu_set); | ||
| 401 | } | ||
| 402 | |||
| 403 | /* | ||
| 404 | * Here we need to explicitely preallocate the counts, as if | ||
| 405 | * we use the auto allocation it will allocate just for 1 cpu, | ||
| 406 | * as we start by cpu 0. | ||
| 407 | */ | ||
| 408 | if (perf_evsel__alloc_counts(evsel, cpus->nr) < 0) { | ||
| 409 | pr_debug("perf_evsel__alloc_counts(ncpus=%d)\n", cpus->nr); | ||
| 410 | goto out_close_fd; | ||
| 411 | } | ||
| 412 | |||
| 413 | err = 0; | ||
| 414 | |||
| 415 | for (cpu = 0; cpu < cpus->nr; ++cpu) { | ||
| 416 | unsigned int expected; | ||
| 417 | |||
| 418 | if (cpus->map[cpu] >= CPU_SETSIZE) | ||
| 419 | continue; | ||
| 420 | |||
| 421 | if (perf_evsel__read_on_cpu(evsel, cpu, 0) < 0) { | ||
| 422 | pr_debug("perf_evsel__read_on_cpu\n"); | ||
| 423 | err = -1; | ||
| 424 | break; | ||
| 425 | } | ||
| 426 | |||
| 427 | expected = nr_open_calls + cpu; | ||
| 428 | if (evsel->counts->cpu[cpu].val != expected) { | ||
| 429 | pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls on cpu %d, got %" PRIu64 "\n", | ||
| 430 | expected, cpus->map[cpu], evsel->counts->cpu[cpu].val); | ||
| 431 | err = -1; | ||
| 432 | } | ||
| 433 | } | ||
| 434 | |||
| 435 | out_close_fd: | ||
| 436 | perf_evsel__close_fd(evsel, 1, threads->nr); | ||
| 437 | out_evsel_delete: | ||
| 438 | perf_evsel__delete(evsel); | ||
| 439 | out_thread_map_delete: | ||
| 440 | thread_map__delete(threads); | ||
| 441 | return err; | ||
| 442 | } | ||
| 443 | |||
| 444 | /* | ||
| 445 | * This test will generate random numbers of calls to some getpid syscalls, | ||
| 446 | * then establish an mmap for a group of events that are created to monitor | ||
| 447 | * the syscalls. | ||
| 448 | * | ||
| 449 | * It will receive the events, using mmap, use its PERF_SAMPLE_ID generated | ||
| 450 | * sample.id field to map back to its respective perf_evsel instance. | ||
| 451 | * | ||
| 452 | * Then it checks if the number of syscalls reported as perf events by | ||
| 453 | * the kernel corresponds to the number of syscalls made. | ||
| 454 | */ | ||
| 455 | static int test__basic_mmap(void) | ||
| 456 | { | ||
| 457 | int err = -1; | ||
| 458 | union perf_event *event; | ||
| 459 | struct thread_map *threads; | ||
| 460 | struct cpu_map *cpus; | ||
| 461 | struct perf_evlist *evlist; | ||
| 462 | struct perf_event_attr attr = { | ||
| 463 | .type = PERF_TYPE_TRACEPOINT, | ||
| 464 | .read_format = PERF_FORMAT_ID, | ||
| 465 | .sample_type = PERF_SAMPLE_ID, | ||
| 466 | .watermark = 0, | ||
| 467 | }; | ||
| 468 | cpu_set_t cpu_set; | ||
| 469 | const char *syscall_names[] = { "getsid", "getppid", "getpgrp", | ||
| 470 | "getpgid", }; | ||
| 471 | pid_t (*syscalls[])(void) = { (void *)getsid, getppid, getpgrp, | ||
| 472 | (void*)getpgid }; | ||
| 473 | #define nsyscalls ARRAY_SIZE(syscall_names) | ||
| 474 | int ids[nsyscalls]; | ||
| 475 | unsigned int nr_events[nsyscalls], | ||
| 476 | expected_nr_events[nsyscalls], i, j; | ||
| 477 | struct perf_evsel *evsels[nsyscalls], *evsel; | ||
| 478 | int sample_size = __perf_evsel__sample_size(attr.sample_type); | ||
| 479 | |||
| 480 | for (i = 0; i < nsyscalls; ++i) { | ||
| 481 | char name[64]; | ||
| 482 | |||
| 483 | snprintf(name, sizeof(name), "sys_enter_%s", syscall_names[i]); | ||
| 484 | ids[i] = trace_event__id(name); | ||
| 485 | if (ids[i] < 0) { | ||
| 486 | pr_debug("Is debugfs mounted on /sys/kernel/debug?\n"); | ||
| 487 | return -1; | ||
| 488 | } | ||
| 489 | nr_events[i] = 0; | ||
| 490 | expected_nr_events[i] = random() % 257; | ||
| 491 | } | ||
| 492 | |||
| 493 | threads = thread_map__new(-1, getpid()); | ||
| 494 | if (threads == NULL) { | ||
| 495 | pr_debug("thread_map__new\n"); | ||
| 496 | return -1; | ||
| 497 | } | ||
| 498 | |||
| 499 | cpus = cpu_map__new(NULL); | ||
| 500 | if (cpus == NULL) { | ||
| 501 | pr_debug("cpu_map__new\n"); | ||
| 502 | goto out_free_threads; | ||
| 503 | } | ||
| 504 | |||
| 505 | CPU_ZERO(&cpu_set); | ||
| 506 | CPU_SET(cpus->map[0], &cpu_set); | ||
| 507 | sched_setaffinity(0, sizeof(cpu_set), &cpu_set); | ||
| 508 | if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) { | ||
| 509 | pr_debug("sched_setaffinity() failed on CPU %d: %s ", | ||
| 510 | cpus->map[0], strerror(errno)); | ||
| 511 | goto out_free_cpus; | ||
| 512 | } | ||
| 513 | |||
| 514 | evlist = perf_evlist__new(cpus, threads); | ||
| 515 | if (evlist == NULL) { | ||
| 516 | pr_debug("perf_evlist__new\n"); | ||
| 517 | goto out_free_cpus; | ||
| 518 | } | ||
| 519 | |||
| 520 | /* anonymous union fields, can't be initialized above */ | ||
| 521 | attr.wakeup_events = 1; | ||
| 522 | attr.sample_period = 1; | ||
| 523 | |||
| 524 | for (i = 0; i < nsyscalls; ++i) { | ||
| 525 | attr.config = ids[i]; | ||
| 526 | evsels[i] = perf_evsel__new(&attr, i); | ||
| 527 | if (evsels[i] == NULL) { | ||
| 528 | pr_debug("perf_evsel__new\n"); | ||
| 529 | goto out_free_evlist; | ||
| 530 | } | ||
| 531 | |||
| 532 | perf_evlist__add(evlist, evsels[i]); | ||
| 533 | |||
| 534 | if (perf_evsel__open(evsels[i], cpus, threads, false) < 0) { | ||
| 535 | pr_debug("failed to open counter: %s, " | ||
| 536 | "tweak /proc/sys/kernel/perf_event_paranoid?\n", | ||
| 537 | strerror(errno)); | ||
| 538 | goto out_close_fd; | ||
| 539 | } | ||
| 540 | } | ||
| 541 | |||
| 542 | if (perf_evlist__mmap(evlist, 128, true) < 0) { | ||
| 543 | pr_debug("failed to mmap events: %d (%s)\n", errno, | ||
| 544 | strerror(errno)); | ||
| 545 | goto out_close_fd; | ||
| 546 | } | ||
| 547 | |||
| 548 | for (i = 0; i < nsyscalls; ++i) | ||
| 549 | for (j = 0; j < expected_nr_events[i]; ++j) { | ||
| 550 | int foo = syscalls[i](); | ||
| 551 | ++foo; | ||
| 552 | } | ||
| 553 | |||
| 554 | while ((event = perf_evlist__mmap_read(evlist, 0)) != NULL) { | ||
| 555 | struct perf_sample sample; | ||
| 556 | |||
| 557 | if (event->header.type != PERF_RECORD_SAMPLE) { | ||
| 558 | pr_debug("unexpected %s event\n", | ||
| 559 | perf_event__name(event->header.type)); | ||
| 560 | goto out_munmap; | ||
| 561 | } | ||
| 562 | |||
| 563 | err = perf_event__parse_sample(event, attr.sample_type, sample_size, | ||
| 564 | false, &sample, false); | ||
| 565 | if (err) { | ||
| 566 | pr_err("Can't parse sample, err = %d\n", err); | ||
| 567 | goto out_munmap; | ||
| 568 | } | ||
| 569 | |||
| 570 | evsel = perf_evlist__id2evsel(evlist, sample.id); | ||
| 571 | if (evsel == NULL) { | ||
| 572 | pr_debug("event with id %" PRIu64 | ||
| 573 | " doesn't map to an evsel\n", sample.id); | ||
| 574 | goto out_munmap; | ||
| 575 | } | ||
| 576 | nr_events[evsel->idx]++; | ||
| 577 | } | ||
| 578 | |||
| 579 | list_for_each_entry(evsel, &evlist->entries, node) { | ||
| 580 | if (nr_events[evsel->idx] != expected_nr_events[evsel->idx]) { | ||
| 581 | pr_debug("expected %d %s events, got %d\n", | ||
| 582 | expected_nr_events[evsel->idx], | ||
| 583 | event_name(evsel), nr_events[evsel->idx]); | ||
| 584 | goto out_munmap; | ||
| 585 | } | ||
| 586 | } | ||
| 587 | |||
| 588 | err = 0; | ||
| 589 | out_munmap: | ||
| 590 | perf_evlist__munmap(evlist); | ||
| 591 | out_close_fd: | ||
| 592 | for (i = 0; i < nsyscalls; ++i) | ||
| 593 | perf_evsel__close_fd(evsels[i], 1, threads->nr); | ||
| 594 | out_free_evlist: | ||
| 595 | perf_evlist__delete(evlist); | ||
| 596 | out_free_cpus: | ||
| 597 | cpu_map__delete(cpus); | ||
| 598 | out_free_threads: | ||
| 599 | thread_map__delete(threads); | ||
| 600 | return err; | ||
| 601 | #undef nsyscalls | ||
| 602 | } | ||
| 603 | |||
| 604 | #define TEST_ASSERT_VAL(text, cond) \ | ||
| 605 | do { \ | ||
| 606 | if (!cond) { \ | ||
| 607 | pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \ | ||
| 608 | return -1; \ | ||
| 609 | } \ | ||
| 610 | } while (0) | ||
| 611 | |||
| 612 | static int test__checkevent_tracepoint(struct perf_evlist *evlist) | ||
| 613 | { | ||
| 614 | struct perf_evsel *evsel = list_entry(evlist->entries.next, | ||
| 615 | struct perf_evsel, node); | ||
| 616 | |||
| 617 | TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); | ||
| 618 | TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->attr.type); | ||
| 619 | TEST_ASSERT_VAL("wrong sample_type", | ||
| 620 | (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | PERF_SAMPLE_CPU) == | ||
| 621 | evsel->attr.sample_type); | ||
| 622 | TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->attr.sample_period); | ||
| 623 | return 0; | ||
| 624 | } | ||
| 625 | |||
| 626 | static int test__checkevent_tracepoint_multi(struct perf_evlist *evlist) | ||
| 627 | { | ||
| 628 | struct perf_evsel *evsel; | ||
| 629 | |||
| 630 | TEST_ASSERT_VAL("wrong number of entries", evlist->nr_entries > 1); | ||
| 631 | |||
| 632 | list_for_each_entry(evsel, &evlist->entries, node) { | ||
| 633 | TEST_ASSERT_VAL("wrong type", | ||
| 634 | PERF_TYPE_TRACEPOINT == evsel->attr.type); | ||
| 635 | TEST_ASSERT_VAL("wrong sample_type", | ||
| 636 | (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | PERF_SAMPLE_CPU) | ||
| 637 | == evsel->attr.sample_type); | ||
| 638 | TEST_ASSERT_VAL("wrong sample_period", | ||
| 639 | 1 == evsel->attr.sample_period); | ||
| 640 | } | ||
| 641 | return 0; | ||
| 642 | } | ||
| 643 | |||
| 644 | static int test__checkevent_raw(struct perf_evlist *evlist) | ||
| 645 | { | ||
| 646 | struct perf_evsel *evsel = list_entry(evlist->entries.next, | ||
| 647 | struct perf_evsel, node); | ||
| 648 | |||
| 649 | TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); | ||
| 650 | TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type); | ||
| 651 | TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); | ||
| 652 | return 0; | ||
| 653 | } | ||
| 654 | |||
| 655 | static int test__checkevent_numeric(struct perf_evlist *evlist) | ||
| 656 | { | ||
| 657 | struct perf_evsel *evsel = list_entry(evlist->entries.next, | ||
| 658 | struct perf_evsel, node); | ||
| 659 | |||
| 660 | TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); | ||
| 661 | TEST_ASSERT_VAL("wrong type", 1 == evsel->attr.type); | ||
| 662 | TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config); | ||
| 663 | return 0; | ||
| 664 | } | ||
| 665 | |||
| 666 | static int test__checkevent_symbolic_name(struct perf_evlist *evlist) | ||
| 667 | { | ||
| 668 | struct perf_evsel *evsel = list_entry(evlist->entries.next, | ||
| 669 | struct perf_evsel, node); | ||
| 670 | |||
| 671 | TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); | ||
| 672 | TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); | ||
| 673 | TEST_ASSERT_VAL("wrong config", | ||
| 674 | PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config); | ||
| 675 | return 0; | ||
| 676 | } | ||
| 677 | |||
| 678 | static int test__checkevent_symbolic_alias(struct perf_evlist *evlist) | ||
| 679 | { | ||
| 680 | struct perf_evsel *evsel = list_entry(evlist->entries.next, | ||
| 681 | struct perf_evsel, node); | ||
| 682 | |||
| 683 | TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); | ||
| 684 | TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->attr.type); | ||
| 685 | TEST_ASSERT_VAL("wrong config", | ||
| 686 | PERF_COUNT_SW_PAGE_FAULTS == evsel->attr.config); | ||
| 687 | return 0; | ||
| 688 | } | ||
| 689 | |||
| 690 | static int test__checkevent_genhw(struct perf_evlist *evlist) | ||
| 691 | { | ||
| 692 | struct perf_evsel *evsel = list_entry(evlist->entries.next, | ||
| 693 | struct perf_evsel, node); | ||
| 694 | |||
| 695 | TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); | ||
| 696 | TEST_ASSERT_VAL("wrong type", PERF_TYPE_HW_CACHE == evsel->attr.type); | ||
| 697 | TEST_ASSERT_VAL("wrong config", (1 << 16) == evsel->attr.config); | ||
| 698 | return 0; | ||
| 699 | } | ||
| 700 | |||
| 701 | static int test__checkevent_breakpoint(struct perf_evlist *evlist) | ||
| 702 | { | ||
| 703 | struct perf_evsel *evsel = list_entry(evlist->entries.next, | ||
| 704 | struct perf_evsel, node); | ||
| 705 | |||
| 706 | TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); | ||
| 707 | TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->attr.type); | ||
| 708 | TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); | ||
| 709 | TEST_ASSERT_VAL("wrong bp_type", (HW_BREAKPOINT_R | HW_BREAKPOINT_W) == | ||
| 710 | evsel->attr.bp_type); | ||
| 711 | TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_4 == | ||
| 712 | evsel->attr.bp_len); | ||
| 713 | return 0; | ||
| 714 | } | ||
| 715 | |||
| 716 | static int test__checkevent_breakpoint_x(struct perf_evlist *evlist) | ||
| 717 | { | ||
| 718 | struct perf_evsel *evsel = list_entry(evlist->entries.next, | ||
| 719 | struct perf_evsel, node); | ||
| 720 | |||
| 721 | TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); | ||
| 722 | TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->attr.type); | ||
| 723 | TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); | ||
| 724 | TEST_ASSERT_VAL("wrong bp_type", | ||
| 725 | HW_BREAKPOINT_X == evsel->attr.bp_type); | ||
| 726 | TEST_ASSERT_VAL("wrong bp_len", sizeof(long) == evsel->attr.bp_len); | ||
| 727 | return 0; | ||
| 728 | } | ||
| 729 | |||
| 730 | static int test__checkevent_breakpoint_r(struct perf_evlist *evlist) | ||
| 731 | { | ||
| 732 | struct perf_evsel *evsel = list_entry(evlist->entries.next, | ||
| 733 | struct perf_evsel, node); | ||
| 734 | |||
| 735 | TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); | ||
| 736 | TEST_ASSERT_VAL("wrong type", | ||
| 737 | PERF_TYPE_BREAKPOINT == evsel->attr.type); | ||
| 738 | TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); | ||
| 739 | TEST_ASSERT_VAL("wrong bp_type", | ||
| 740 | HW_BREAKPOINT_R == evsel->attr.bp_type); | ||
| 741 | TEST_ASSERT_VAL("wrong bp_len", | ||
| 742 | HW_BREAKPOINT_LEN_4 == evsel->attr.bp_len); | ||
| 743 | return 0; | ||
| 744 | } | ||
| 745 | |||
| 746 | static int test__checkevent_breakpoint_w(struct perf_evlist *evlist) | ||
| 747 | { | ||
| 748 | struct perf_evsel *evsel = list_entry(evlist->entries.next, | ||
| 749 | struct perf_evsel, node); | ||
| 750 | |||
| 751 | TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries); | ||
| 752 | TEST_ASSERT_VAL("wrong type", | ||
| 753 | PERF_TYPE_BREAKPOINT == evsel->attr.type); | ||
| 754 | TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config); | ||
| 755 | TEST_ASSERT_VAL("wrong bp_type", | ||
| 756 | HW_BREAKPOINT_W == evsel->attr.bp_type); | ||
| 757 | TEST_ASSERT_VAL("wrong bp_len", | ||
| 758 | HW_BREAKPOINT_LEN_4 == evsel->attr.bp_len); | ||
| 759 | return 0; | ||
| 760 | } | ||
| 761 | |||
| 762 | static struct test__event_st { | ||
| 763 | const char *name; | ||
| 764 | __u32 type; | ||
| 765 | int (*check)(struct perf_evlist *evlist); | ||
| 766 | } test__events[] = { | ||
| 767 | { | ||
| 768 | .name = "syscalls:sys_enter_open", | ||
| 769 | .check = test__checkevent_tracepoint, | ||
| 770 | }, | ||
| 771 | { | ||
| 772 | .name = "syscalls:*", | ||
| 773 | .check = test__checkevent_tracepoint_multi, | ||
| 774 | }, | ||
| 775 | { | ||
| 776 | .name = "r1", | ||
| 777 | .check = test__checkevent_raw, | ||
| 778 | }, | ||
| 779 | { | ||
| 780 | .name = "1:1", | ||
| 781 | .check = test__checkevent_numeric, | ||
| 782 | }, | ||
| 783 | { | ||
| 784 | .name = "instructions", | ||
| 785 | .check = test__checkevent_symbolic_name, | ||
| 786 | }, | ||
| 787 | { | ||
| 788 | .name = "faults", | ||
| 789 | .check = test__checkevent_symbolic_alias, | ||
| 790 | }, | ||
| 791 | { | ||
| 792 | .name = "L1-dcache-load-miss", | ||
| 793 | .check = test__checkevent_genhw, | ||
| 794 | }, | ||
| 795 | { | ||
| 796 | .name = "mem:0", | ||
| 797 | .check = test__checkevent_breakpoint, | ||
| 798 | }, | ||
| 799 | { | ||
| 800 | .name = "mem:0:x", | ||
| 801 | .check = test__checkevent_breakpoint_x, | ||
| 802 | }, | ||
| 803 | { | ||
| 804 | .name = "mem:0:r", | ||
| 805 | .check = test__checkevent_breakpoint_r, | ||
| 806 | }, | ||
| 807 | { | ||
| 808 | .name = "mem:0:w", | ||
| 809 | .check = test__checkevent_breakpoint_w, | ||
| 810 | }, | ||
| 811 | }; | ||
| 812 | |||
| 813 | #define TEST__EVENTS_CNT (sizeof(test__events) / sizeof(struct test__event_st)) | ||
| 814 | |||
| 815 | static int test__parse_events(void) | ||
| 816 | { | ||
| 817 | struct perf_evlist *evlist; | ||
| 818 | u_int i; | ||
| 819 | int ret = 0; | ||
| 820 | |||
| 821 | for (i = 0; i < TEST__EVENTS_CNT; i++) { | ||
| 822 | struct test__event_st *e = &test__events[i]; | ||
| 823 | |||
| 824 | evlist = perf_evlist__new(NULL, NULL); | ||
| 825 | if (evlist == NULL) | ||
| 826 | break; | ||
| 827 | |||
| 828 | ret = parse_events(evlist, e->name, 0); | ||
| 829 | if (ret) { | ||
| 830 | pr_debug("failed to parse event '%s', err %d\n", | ||
| 831 | e->name, ret); | ||
| 832 | break; | ||
| 833 | } | ||
| 834 | |||
| 835 | ret = e->check(evlist); | ||
| 836 | if (ret) | ||
| 837 | break; | ||
| 838 | |||
| 839 | perf_evlist__delete(evlist); | ||
| 840 | } | ||
| 841 | |||
| 842 | return ret; | ||
| 843 | } | ||
| 844 | static struct test { | ||
| 845 | const char *desc; | ||
| 846 | int (*func)(void); | ||
| 847 | } tests[] = { | ||
| 848 | { | ||
| 849 | .desc = "vmlinux symtab matches kallsyms", | ||
| 850 | .func = test__vmlinux_matches_kallsyms, | ||
| 851 | }, | ||
| 852 | { | ||
| 853 | .desc = "detect open syscall event", | ||
| 854 | .func = test__open_syscall_event, | ||
| 855 | }, | ||
| 856 | { | ||
| 857 | .desc = "detect open syscall event on all cpus", | ||
| 858 | .func = test__open_syscall_event_on_all_cpus, | ||
| 859 | }, | ||
| 860 | { | ||
| 861 | .desc = "read samples using the mmap interface", | ||
| 862 | .func = test__basic_mmap, | ||
| 863 | }, | ||
| 864 | { | ||
| 865 | .desc = "parse events tests", | ||
| 866 | .func = test__parse_events, | ||
| 867 | }, | ||
| 868 | { | ||
| 869 | .func = NULL, | ||
| 870 | }, | ||
| 871 | }; | ||
| 872 | |||
| 873 | static int __cmd_test(void) | ||
| 874 | { | ||
| 875 | int i = 0; | ||
| 876 | |||
| 877 | page_size = sysconf(_SC_PAGE_SIZE); | ||
| 878 | |||
| 879 | while (tests[i].func) { | ||
| 880 | int err; | ||
| 881 | pr_info("%2d: %s:", i + 1, tests[i].desc); | ||
| 882 | pr_debug("\n--- start ---\n"); | ||
| 883 | err = tests[i].func(); | ||
| 884 | pr_debug("---- end ----\n%s:", tests[i].desc); | ||
| 885 | pr_info(" %s\n", err ? "FAILED!\n" : "Ok"); | ||
| 886 | ++i; | ||
| 887 | } | ||
| 888 | |||
| 889 | return 0; | ||
| 890 | } | ||
| 891 | |||
| 892 | static const char * const test_usage[] = { | ||
| 893 | "perf test [<options>]", | ||
| 894 | NULL, | ||
| 895 | }; | ||
| 896 | |||
| 897 | static const struct option test_options[] = { | ||
| 898 | OPT_INTEGER('v', "verbose", &verbose, | ||
| 899 | "be more verbose (show symbol address, etc)"), | ||
| 900 | OPT_END() | ||
| 901 | }; | ||
| 902 | |||
| 903 | int cmd_test(int argc, const char **argv, const char *prefix __used) | ||
| 904 | { | ||
| 905 | argc = parse_options(argc, argv, test_options, test_usage, 0); | ||
| 906 | if (argc) | ||
| 907 | usage_with_options(test_usage, test_options); | ||
| 908 | |||
| 909 | symbol_conf.priv_size = sizeof(int); | ||
| 910 | symbol_conf.sort_by_name = true; | ||
| 911 | symbol_conf.try_vmlinux_path = true; | ||
| 912 | |||
| 913 | if (symbol__init() < 0) | ||
| 914 | return -1; | ||
| 915 | |||
| 916 | setup_pager(); | ||
| 917 | |||
| 918 | return __cmd_test(); | ||
| 919 | } | ||
diff --git a/tools/perf/util/include/linux/module.h b/tools/perf/util/include/linux/module.h new file mode 100644 index 00000000000..b43e2dc21e0 --- /dev/null +++ b/tools/perf/util/include/linux/module.h | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #ifndef PERF_LINUX_MODULE_H | ||
| 2 | #define PERF_LINUX_MODULE_H | ||
| 3 | |||
| 4 | #define EXPORT_SYMBOL(name) | ||
| 5 | |||
| 6 | #endif | ||
diff --git a/tools/perf/util/ui/browser.c b/tools/perf/util/ui/browser.c new file mode 100644 index 00000000000..611219f8068 --- /dev/null +++ b/tools/perf/util/ui/browser.c | |||
| @@ -0,0 +1,356 @@ | |||
| 1 | #include "libslang.h" | ||
| 2 | #include "ui.h" | ||
| 3 | #include <linux/compiler.h> | ||
| 4 | #include <linux/list.h> | ||
| 5 | #include <linux/rbtree.h> | ||
| 6 | #include <stdlib.h> | ||
| 7 | #include <sys/ttydefaults.h> | ||
| 8 | #include "browser.h" | ||
| 9 | #include "helpline.h" | ||
| 10 | #include "../color.h" | ||
| 11 | #include "../util.h" | ||
| 12 | #include <stdio.h> | ||
| 13 | |||
| 14 | static int ui_browser__percent_color(double percent, bool current) | ||
| 15 | { | ||
| 16 | if (current) | ||
| 17 | return HE_COLORSET_SELECTED; | ||
| 18 | if (percent >= MIN_RED) | ||
| 19 | return HE_COLORSET_TOP; | ||
| 20 | if (percent >= MIN_GREEN) | ||
| 21 | return HE_COLORSET_MEDIUM; | ||
| 22 | return HE_COLORSET_NORMAL; | ||
| 23 | } | ||
| 24 | |||
| 25 | void ui_browser__set_color(struct ui_browser *self __used, int color) | ||
| 26 | { | ||
| 27 | SLsmg_set_color(color); | ||
| 28 | } | ||
| 29 | |||
| 30 | void ui_browser__set_percent_color(struct ui_browser *self, | ||
| 31 | double percent, bool current) | ||
| 32 | { | ||
| 33 | int color = ui_browser__percent_color(percent, current); | ||
| 34 | ui_browser__set_color(self, color); | ||
| 35 | } | ||
| 36 | |||
| 37 | void ui_browser__gotorc(struct ui_browser *self, int y, int x) | ||
| 38 | { | ||
| 39 | SLsmg_gotorc(self->y + y, self->x + x); | ||
| 40 | } | ||
| 41 | |||
| 42 | void ui_browser__list_head_seek(struct ui_browser *self, off_t offset, int whence) | ||
| 43 | { | ||
| 44 | struct list_head *head = self->entries; | ||
| 45 | struct list_head *pos; | ||
| 46 | |||
| 47 | switch (whence) { | ||
| 48 | case SEEK_SET: | ||
| 49 | pos = head->next; | ||
| 50 | break; | ||
| 51 | case SEEK_CUR: | ||
| 52 | pos = self->top; | ||
| 53 | break; | ||
| 54 | case SEEK_END: | ||
| 55 | pos = head->prev; | ||
| 56 | break; | ||
| 57 | default: | ||
| 58 | return; | ||
| 59 | } | ||
| 60 | |||
| 61 | if (offset > 0) { | ||
| 62 | while (offset-- != 0) | ||
| 63 | pos = pos->next; | ||
| 64 | } else { | ||
| 65 | while (offset++ != 0) | ||
| 66 | pos = pos->prev; | ||
| 67 | } | ||
| 68 | |||
| 69 | self->top = pos; | ||
| 70 | } | ||
| 71 | |||
| 72 | void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence) | ||
| 73 | { | ||
| 74 | struct rb_root *root = self->entries; | ||
| 75 | struct rb_node *nd; | ||
| 76 | |||
| 77 | switch (whence) { | ||
| 78 | case SEEK_SET: | ||
| 79 | nd = rb_first(root); | ||
| 80 | break; | ||
| 81 | case SEEK_CUR: | ||
| 82 | nd = self->top; | ||
| 83 | break; | ||
| 84 | case SEEK_END: | ||
| 85 | nd = rb_last(root); | ||
| 86 | break; | ||
| 87 | default: | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | |||
| 91 | if (offset > 0) { | ||
| 92 | while (offset-- != 0) | ||
| 93 | nd = rb_next(nd); | ||
| 94 | } else { | ||
| 95 | while (offset++ != 0) | ||
| 96 | nd = rb_prev(nd); | ||
| 97 | } | ||
| 98 | |||
| 99 | self->top = nd; | ||
| 100 | } | ||
| 101 | |||
| 102 | unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self) | ||
| 103 | { | ||
| 104 | struct rb_node *nd; | ||
| 105 | int row = 0; | ||
| 106 | |||
| 107 | if (self->top == NULL) | ||
| 108 | self->top = rb_first(self->entries); | ||
| 109 | |||
| 110 | nd = self->top; | ||
| 111 | |||
| 112 | while (nd != NULL) { | ||
| 113 | ui_browser__gotorc(self, row, 0); | ||
| 114 | self->write(self, nd, row); | ||
| 115 | if (++row == self->height) | ||
| 116 | break; | ||
| 117 | nd = rb_next(nd); | ||
| 118 | } | ||
| 119 | |||
| 120 | return row; | ||
| 121 | } | ||
| 122 | |||
| 123 | bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row) | ||
| 124 | { | ||
| 125 | return self->top_idx + row == self->index; | ||
| 126 | } | ||
| 127 | |||
| 128 | void ui_browser__refresh_dimensions(struct ui_browser *self) | ||
| 129 | { | ||
| 130 | int cols, rows; | ||
| 131 | newtGetScreenSize(&cols, &rows); | ||
| 132 | |||
| 133 | self->width = cols - 1; | ||
| 134 | self->height = rows - 2; | ||
| 135 | self->y = 1; | ||
| 136 | self->x = 0; | ||
| 137 | } | ||
| 138 | |||
| 139 | void ui_browser__reset_index(struct ui_browser *self) | ||
| 140 | { | ||
| 141 | self->index = self->top_idx = 0; | ||
| 142 | self->seek(self, 0, SEEK_SET); | ||
| 143 | } | ||
| 144 | |||
| 145 | void ui_browser__add_exit_key(struct ui_browser *self, int key) | ||
| 146 | { | ||
| 147 | newtFormAddHotKey(self->form, key); | ||
| 148 | } | ||
| 149 | |||
| 150 | void ui_browser__add_exit_keys(struct ui_browser *self, int keys[]) | ||
| 151 | { | ||
| 152 | int i = 0; | ||
| 153 | |||
| 154 | while (keys[i] && i < 64) { | ||
| 155 | ui_browser__add_exit_key(self, keys[i]); | ||
| 156 | ++i; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | void __ui_browser__show_title(struct ui_browser *browser, const char *title) | ||
| 161 | { | ||
| 162 | SLsmg_gotorc(0, 0); | ||
| 163 | ui_browser__set_color(browser, NEWT_COLORSET_ROOT); | ||
| 164 | slsmg_write_nstring(title, browser->width); | ||
| 165 | } | ||
| 166 | |||
| 167 | void ui_browser__show_title(struct ui_browser *browser, const char *title) | ||
| 168 | { | ||
| 169 | pthread_mutex_lock(&ui__lock); | ||
| 170 | __ui_browser__show_title(browser, title); | ||
| 171 | pthread_mutex_unlock(&ui__lock); | ||
| 172 | } | ||
| 173 | |||
| 174 | int ui_browser__show(struct ui_browser *self, const char *title, | ||
| 175 | const char *helpline, ...) | ||
| 176 | { | ||
| 177 | va_list ap; | ||
| 178 | int keys[] = { NEWT_KEY_UP, NEWT_KEY_DOWN, NEWT_KEY_PGUP, | ||
| 179 | NEWT_KEY_PGDN, NEWT_KEY_HOME, NEWT_KEY_END, ' ', | ||
| 180 | NEWT_KEY_LEFT, NEWT_KEY_ESCAPE, 'q', CTRL('c'), 0 }; | ||
| 181 | |||
| 182 | if (self->form != NULL) | ||
| 183 | newtFormDestroy(self->form); | ||
| 184 | |||
| 185 | ui_browser__refresh_dimensions(self); | ||
| 186 | self->form = newtForm(NULL, NULL, 0); | ||
| 187 | if (self->form == NULL) | ||
| 188 | return -1; | ||
| 189 | |||
| 190 | self->sb = newtVerticalScrollbar(self->width, 1, self->height, | ||
| 191 | HE_COLORSET_NORMAL, | ||
| 192 | HE_COLORSET_SELECTED); | ||
| 193 | if (self->sb == NULL) | ||
| 194 | return -1; | ||
| 195 | |||
| 196 | pthread_mutex_lock(&ui__lock); | ||
| 197 | __ui_browser__show_title(self, title); | ||
| 198 | |||
| 199 | ui_browser__add_exit_keys(self, keys); | ||
| 200 | newtFormAddComponent(self->form, self->sb); | ||
| 201 | |||
| 202 | va_start(ap, helpline); | ||
| 203 | ui_helpline__vpush(helpline, ap); | ||
| 204 | va_end(ap); | ||
| 205 | pthread_mutex_unlock(&ui__lock); | ||
| 206 | return 0; | ||
| 207 | } | ||
| 208 | |||
| 209 | void ui_browser__hide(struct ui_browser *self) | ||
| 210 | { | ||
| 211 | pthread_mutex_lock(&ui__lock); | ||
| 212 | newtFormDestroy(self->form); | ||
| 213 | self->form = NULL; | ||
| 214 | ui_helpline__pop(); | ||
| 215 | pthread_mutex_unlock(&ui__lock); | ||
| 216 | } | ||
| 217 | |||
| 218 | int ui_browser__refresh(struct ui_browser *self) | ||
| 219 | { | ||
| 220 | int row; | ||
| 221 | |||
| 222 | pthread_mutex_lock(&ui__lock); | ||
| 223 | newtScrollbarSet(self->sb, self->index, self->nr_entries - 1); | ||
| 224 | row = self->refresh(self); | ||
| 225 | ui_browser__set_color(self, HE_COLORSET_NORMAL); | ||
| 226 | SLsmg_fill_region(self->y + row, self->x, | ||
| 227 | self->height - row, self->width, ' '); | ||
| 228 | pthread_mutex_unlock(&ui__lock); | ||
| 229 | |||
| 230 | return 0; | ||
| 231 | } | ||
| 232 | |||
| 233 | int ui_browser__run(struct ui_browser *self) | ||
| 234 | { | ||
| 235 | struct newtExitStruct es; | ||
| 236 | |||
| 237 | if (ui_browser__refresh(self) < 0) | ||
| 238 | return -1; | ||
| 239 | |||
| 240 | while (1) { | ||
| 241 | off_t offset; | ||
| 242 | |||
| 243 | newtFormRun(self->form, &es); | ||
| 244 | |||
| 245 | if (es.reason != NEWT_EXIT_HOTKEY) | ||
| 246 | break; | ||
| 247 | switch (es.u.key) { | ||
| 248 | case NEWT_KEY_DOWN: | ||
| 249 | if (self->index == self->nr_entries - 1) | ||
| 250 | break; | ||
| 251 | ++self->index; | ||
| 252 | if (self->index == self->top_idx + self->height) { | ||
| 253 | ++self->top_idx; | ||
| 254 | self->seek(self, +1, SEEK_CUR); | ||
| 255 | } | ||
| 256 | break; | ||
| 257 | case NEWT_KEY_UP: | ||
| 258 | if (self->index == 0) | ||
| 259 | break; | ||
| 260 | --self->index; | ||
| 261 | if (self->index < self->top_idx) { | ||
| 262 | --self->top_idx; | ||
| 263 | self->seek(self, -1, SEEK_CUR); | ||
| 264 | } | ||
| 265 | break; | ||
| 266 | case NEWT_KEY_PGDN: | ||
| 267 | case ' ': | ||
| 268 | if (self->top_idx + self->height > self->nr_entries - 1) | ||
| 269 | break; | ||
| 270 | |||
| 271 | offset = self->height; | ||
| 272 | if (self->index + offset > self->nr_entries - 1) | ||
| 273 | offset = self->nr_entries - 1 - self->index; | ||
| 274 | self->index += offset; | ||
| 275 | self->top_idx += offset; | ||
| 276 | self->seek(self, +offset, SEEK_CUR); | ||
| 277 | break; | ||
| 278 | case NEWT_KEY_PGUP: | ||
| 279 | if (self->top_idx == 0) | ||
| 280 | break; | ||
| 281 | |||
| 282 | if (self->top_idx < self->height) | ||
| 283 | offset = self->top_idx; | ||
| 284 | else | ||
| 285 | offset = self->height; | ||
| 286 | |||
| 287 | self->index -= offset; | ||
| 288 | self->top_idx -= offset; | ||
| 289 | self->seek(self, -offset, SEEK_CUR); | ||
| 290 | break; | ||
| 291 | case NEWT_KEY_HOME: | ||
| 292 | ui_browser__reset_index(self); | ||
| 293 | break; | ||
| 294 | case NEWT_KEY_END: | ||
| 295 | offset = self->height - 1; | ||
| 296 | if (offset >= self->nr_entries) | ||
| 297 | offset = self->nr_entries - 1; | ||
| 298 | |||
| 299 | self->index = self->nr_entries - 1; | ||
| 300 | self->top_idx = self->index - offset; | ||
| 301 | self->seek(self, -offset, SEEK_END); | ||
| 302 | break; | ||
| 303 | default: | ||
| 304 | return es.u.key; | ||
| 305 | } | ||
| 306 | if (ui_browser__refresh(self) < 0) | ||
| 307 | return -1; | ||
| 308 | } | ||
| 309 | return -1; | ||
| 310 | } | ||
| 311 | |||
| 312 | unsigned int ui_browser__list_head_refresh(struct ui_browser *self) | ||
| 313 | { | ||
| 314 | struct list_head *pos; | ||
| 315 | struct list_head *head = self->entries; | ||
| 316 | int row = 0; | ||
| 317 | |||
| 318 | if (self->top == NULL || self->top == self->entries) | ||
| 319 | self->top = head->next; | ||
| 320 | |||
| 321 | pos = self->top; | ||
| 322 | |||
| 323 | list_for_each_from(pos, head) { | ||
| 324 | ui_browser__gotorc(self, row, 0); | ||
| 325 | self->write(self, pos, row); | ||
| 326 | if (++row == self->height) | ||
| 327 | break; | ||
| 328 | } | ||
| 329 | |||
| 330 | return row; | ||
| 331 | } | ||
| 332 | |||
| 333 | static struct newtPercentTreeColors { | ||
| 334 | const char *topColorFg, *topColorBg; | ||
| 335 | const char *mediumColorFg, *mediumColorBg; | ||
| 336 | const char *normalColorFg, *normalColorBg; | ||
| 337 | const char *selColorFg, *selColorBg; | ||
| 338 | const char *codeColorFg, *codeColorBg; | ||
| 339 | } defaultPercentTreeColors = { | ||
| 340 | "red", "lightgray", | ||
| 341 | "green", "lightgray", | ||
| 342 | "black", "lightgray", | ||
| 343 | "lightgray", "magenta", | ||
| 344 | "blue", "lightgray", | ||
| 345 | }; | ||
| 346 | |||
| 347 | void ui_browser__init(void) | ||
| 348 | { | ||
| 349 | struct newtPercentTreeColors *c = &defaultPercentTreeColors; | ||
| 350 | |||
| 351 | sltt_set_color(HE_COLORSET_TOP, NULL, c->topColorFg, c->topColorBg); | ||
| 352 | sltt_set_color(HE_COLORSET_MEDIUM, NULL, c->mediumColorFg, c->mediumColorBg); | ||
| 353 | sltt_set_color(HE_COLORSET_NORMAL, NULL, c->normalColorFg, c->normalColorBg); | ||
| 354 | sltt_set_color(HE_COLORSET_SELECTED, NULL, c->selColorFg, c->selColorBg); | ||
| 355 | sltt_set_color(HE_COLORSET_CODE, NULL, c->codeColorFg, c->codeColorBg); | ||
| 356 | } | ||
diff --git a/tools/perf/util/ui/browser.h b/tools/perf/util/ui/browser.h new file mode 100644 index 00000000000..fc63dda1091 --- /dev/null +++ b/tools/perf/util/ui/browser.h | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | #ifndef _PERF_UI_BROWSER_H_ | ||
| 2 | #define _PERF_UI_BROWSER_H_ 1 | ||
| 3 | |||
| 4 | #include <stdbool.h> | ||
| 5 | #include <newt.h> | ||
| 6 | #include <sys/types.h> | ||
| 7 | #include "../types.h" | ||
| 8 | |||
| 9 | #define HE_COLORSET_TOP 50 | ||
| 10 | #define HE_COLORSET_MEDIUM 51 | ||
| 11 | #define HE_COLORSET_NORMAL 52 | ||
| 12 | #define HE_COLORSET_SELECTED 53 | ||
| 13 | #define HE_COLORSET_CODE 54 | ||
| 14 | |||
| 15 | struct ui_browser { | ||
| 16 | newtComponent form, sb; | ||
| 17 | u64 index, top_idx; | ||
| 18 | void *top, *entries; | ||
| 19 | u16 y, x, width, height; | ||
| 20 | void *priv; | ||
| 21 | unsigned int (*refresh)(struct ui_browser *self); | ||
| 22 | void (*write)(struct ui_browser *self, void *entry, int row); | ||
| 23 | void (*seek)(struct ui_browser *self, off_t offset, int whence); | ||
| 24 | u32 nr_entries; | ||
| 25 | }; | ||
| 26 | |||
| 27 | void ui_browser__set_color(struct ui_browser *self, int color); | ||
| 28 | void ui_browser__set_percent_color(struct ui_browser *self, | ||
| 29 | double percent, bool current); | ||
| 30 | bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row); | ||
| 31 | void ui_browser__refresh_dimensions(struct ui_browser *self); | ||
| 32 | void ui_browser__reset_index(struct ui_browser *self); | ||
| 33 | |||
| 34 | void ui_browser__gotorc(struct ui_browser *self, int y, int x); | ||
| 35 | void ui_browser__add_exit_key(struct ui_browser *self, int key); | ||
| 36 | void ui_browser__add_exit_keys(struct ui_browser *self, int keys[]); | ||
| 37 | void __ui_browser__show_title(struct ui_browser *browser, const char *title); | ||
| 38 | void ui_browser__show_title(struct ui_browser *browser, const char *title); | ||
| 39 | int ui_browser__show(struct ui_browser *self, const char *title, | ||
| 40 | const char *helpline, ...); | ||
| 41 | void ui_browser__hide(struct ui_browser *self); | ||
| 42 | int ui_browser__refresh(struct ui_browser *self); | ||
| 43 | int ui_browser__run(struct ui_browser *self); | ||
| 44 | |||
| 45 | void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence); | ||
| 46 | unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self); | ||
| 47 | |||
| 48 | void ui_browser__list_head_seek(struct ui_browser *self, off_t offset, int whence); | ||
| 49 | unsigned int ui_browser__list_head_refresh(struct ui_browser *self); | ||
| 50 | |||
| 51 | void ui_browser__init(void); | ||
| 52 | #endif /* _PERF_UI_BROWSER_H_ */ | ||
diff --git a/tools/perf/util/ui/browsers/annotate.c b/tools/perf/util/ui/browsers/annotate.c new file mode 100644 index 00000000000..0229723aceb --- /dev/null +++ b/tools/perf/util/ui/browsers/annotate.c | |||
| @@ -0,0 +1,302 @@ | |||
| 1 | #include "../browser.h" | ||
| 2 | #include "../helpline.h" | ||
| 3 | #include "../libslang.h" | ||
| 4 | #include "../../annotate.h" | ||
| 5 | #include "../../hist.h" | ||
| 6 | #include "../../sort.h" | ||
| 7 | #include "../../symbol.h" | ||
| 8 | #include <pthread.h> | ||
| 9 | |||
| 10 | static void ui__error_window(const char *fmt, ...) | ||
| 11 | { | ||
| 12 | va_list ap; | ||
| 13 | |||
| 14 | va_start(ap, fmt); | ||
| 15 | newtWinMessagev((char *)"Error", (char *)"Ok", (char *)fmt, ap); | ||
| 16 | va_end(ap); | ||
| 17 | } | ||
| 18 | |||
| 19 | struct annotate_browser { | ||
| 20 | struct ui_browser b; | ||
| 21 | struct rb_root entries; | ||
| 22 | struct rb_node *curr_hot; | ||
| 23 | }; | ||
| 24 | |||
| 25 | struct objdump_line_rb_node { | ||
| 26 | struct rb_node rb_node; | ||
| 27 | double percent; | ||
| 28 | u32 idx; | ||
| 29 | }; | ||
| 30 | |||
| 31 | static inline | ||
| 32 | struct objdump_line_rb_node *objdump_line__rb(struct objdump_line *self) | ||
| 33 | { | ||
| 34 | return (struct objdump_line_rb_node *)(self + 1); | ||
| 35 | } | ||
| 36 | |||
| 37 | static void annotate_browser__write(struct ui_browser *self, void *entry, int row) | ||
| 38 | { | ||
| 39 | struct objdump_line *ol = rb_entry(entry, struct objdump_line, node); | ||
| 40 | bool current_entry = ui_browser__is_current_entry(self, row); | ||
| 41 | int width = self->width; | ||
| 42 | |||
| 43 | if (ol->offset != -1) { | ||
| 44 | struct objdump_line_rb_node *olrb = objdump_line__rb(ol); | ||
| 45 | ui_browser__set_percent_color(self, olrb->percent, current_entry); | ||
| 46 | slsmg_printf(" %7.2f ", olrb->percent); | ||
| 47 | } else { | ||
| 48 | ui_browser__set_percent_color(self, 0, current_entry); | ||
| 49 | slsmg_write_nstring(" ", 9); | ||
| 50 | } | ||
| 51 | |||
| 52 | SLsmg_write_char(':'); | ||
| 53 | slsmg_write_nstring(" ", 8); | ||
| 54 | if (!*ol->line) | ||
| 55 | slsmg_write_nstring(" ", width - 18); | ||
| 56 | else | ||
| 57 | slsmg_write_nstring(ol->line, width - 18); | ||
| 58 | |||
| 59 | if (!current_entry) | ||
| 60 | ui_browser__set_color(self, HE_COLORSET_CODE); | ||
| 61 | } | ||
| 62 | |||
| 63 | static double objdump_line__calc_percent(struct objdump_line *self, | ||
| 64 | struct symbol *sym, int evidx) | ||
| 65 | { | ||
| 66 | double percent = 0.0; | ||
| 67 | |||
| 68 | if (self->offset != -1) { | ||
| 69 | int len = sym->end - sym->start; | ||
| 70 | unsigned int hits = 0; | ||
| 71 | struct annotation *notes = symbol__annotation(sym); | ||
| 72 | struct source_line *src_line = notes->src->lines; | ||
| 73 | struct sym_hist *h = annotation__histogram(notes, evidx); | ||
| 74 | s64 offset = self->offset; | ||
| 75 | struct objdump_line *next; | ||
| 76 | |||
| 77 | next = objdump__get_next_ip_line(¬es->src->source, self); | ||
| 78 | while (offset < (s64)len && | ||
| 79 | (next == NULL || offset < next->offset)) { | ||
| 80 | if (src_line) { | ||
| 81 | percent += src_line[offset].percent; | ||
| 82 | } else | ||
| 83 | hits += h->addr[offset]; | ||
| 84 | |||
| 85 | ++offset; | ||
| 86 | } | ||
| 87 | /* | ||
| 88 | * If the percentage wasn't already calculated in | ||
| 89 | * symbol__get_source_line, do it now: | ||
| 90 | */ | ||
| 91 | if (src_line == NULL && h->sum) | ||
| 92 | percent = 100.0 * hits / h->sum; | ||
| 93 | } | ||
| 94 | |||
| 95 | return percent; | ||
| 96 | } | ||
| 97 | |||
| 98 | static void objdump__insert_line(struct rb_root *self, | ||
| 99 | struct objdump_line_rb_node *line) | ||
| 100 | { | ||
| 101 | struct rb_node **p = &self->rb_node; | ||
| 102 | struct rb_node *parent = NULL; | ||
| 103 | struct objdump_line_rb_node *l; | ||
| 104 | |||
| 105 | while (*p != NULL) { | ||
| 106 | parent = *p; | ||
| 107 | l = rb_entry(parent, struct objdump_line_rb_node, rb_node); | ||
| 108 | if (line->percent < l->percent) | ||
| 109 | p = &(*p)->rb_left; | ||
| 110 | else | ||
| 111 | p = &(*p)->rb_right; | ||
| 112 | } | ||
| 113 | rb_link_node(&line->rb_node, parent, p); | ||
| 114 | rb_insert_color(&line->rb_node, self); | ||
| 115 | } | ||
| 116 | |||
| 117 | static void annotate_browser__set_top(struct annotate_browser *self, | ||
| 118 | struct rb_node *nd) | ||
| 119 | { | ||
| 120 | struct objdump_line_rb_node *rbpos; | ||
| 121 | struct objdump_line *pos; | ||
| 122 | unsigned back; | ||
| 123 | |||
| 124 | ui_browser__refresh_dimensions(&self->b); | ||
| 125 | back = self->b.height / 2; | ||
| 126 | rbpos = rb_entry(nd, struct objdump_line_rb_node, rb_node); | ||
| 127 | pos = ((struct objdump_line *)rbpos) - 1; | ||
| 128 | self->b.top_idx = self->b.index = rbpos->idx; | ||
| 129 | |||
| 130 | while (self->b.top_idx != 0 && back != 0) { | ||
| 131 | pos = list_entry(pos->node.prev, struct objdump_line, node); | ||
| 132 | |||
| 133 | --self->b.top_idx; | ||
| 134 | --back; | ||
| 135 | } | ||
| 136 | |||
| 137 | self->b.top = pos; | ||
| 138 | self->curr_hot = nd; | ||
| 139 | } | ||
| 140 | |||
| 141 | static void annotate_browser__calc_percent(struct annotate_browser *browser, | ||
| 142 | int evidx) | ||
| 143 | { | ||
| 144 | struct symbol *sym = browser->b.priv; | ||
| 145 | struct annotation *notes = symbol__annotation(sym); | ||
| 146 | struct objdump_line *pos; | ||
| 147 | |||
| 148 | browser->entries = RB_ROOT; | ||
| 149 | |||
| 150 | pthread_mutex_lock(¬es->lock); | ||
| 151 | |||
| 152 | list_for_each_entry(pos, ¬es->src->source, node) { | ||
| 153 | struct objdump_line_rb_node *rbpos = objdump_line__rb(pos); | ||
| 154 | rbpos->percent = objdump_line__calc_percent(pos, sym, evidx); | ||
| 155 | if (rbpos->percent < 0.01) { | ||
| 156 | RB_CLEAR_NODE(&rbpos->rb_node); | ||
| 157 | continue; | ||
| 158 | } | ||
| 159 | objdump__insert_line(&browser->entries, rbpos); | ||
| 160 | } | ||
| 161 | pthread_mutex_unlock(¬es->lock); | ||
| 162 | |||
| 163 | browser->curr_hot = rb_last(&browser->entries); | ||
| 164 | } | ||
| 165 | |||
| 166 | static int annotate_browser__run(struct annotate_browser *self, int evidx, | ||
| 167 | int refresh) | ||
| 168 | { | ||
| 169 | struct rb_node *nd = NULL; | ||
| 170 | struct symbol *sym = self->b.priv; | ||
| 171 | /* | ||
| 172 | * RIGHT To allow builtin-annotate to cycle thru multiple symbols by | ||
| 173 | * examining the exit key for this function. | ||
| 174 | */ | ||
| 175 | int exit_keys[] = { 'H', NEWT_KEY_TAB, NEWT_KEY_UNTAB, | ||
| 176 | NEWT_KEY_RIGHT, 0 }; | ||
| 177 | int key; | ||
| 178 | |||
| 179 | if (ui_browser__show(&self->b, sym->name, | ||
| 180 | "<-, -> or ESC: exit, TAB/shift+TAB: " | ||
| 181 | "cycle hottest lines, H: Hottest") < 0) | ||
| 182 | return -1; | ||
| 183 | |||
| 184 | ui_browser__add_exit_keys(&self->b, exit_keys); | ||
| 185 | annotate_browser__calc_percent(self, evidx); | ||
| 186 | |||
| 187 | if (self->curr_hot) | ||
| 188 | annotate_browser__set_top(self, self->curr_hot); | ||
| 189 | |||
| 190 | nd = self->curr_hot; | ||
| 191 | |||
| 192 | if (refresh != 0) | ||
| 193 | newtFormSetTimer(self->b.form, refresh); | ||
| 194 | |||
| 195 | while (1) { | ||
| 196 | key = ui_browser__run(&self->b); | ||
| 197 | |||
| 198 | if (refresh != 0) { | ||
| 199 | annotate_browser__calc_percent(self, evidx); | ||
| 200 | /* | ||
| 201 | * Current line focus got out of the list of most active | ||
| 202 | * lines, NULL it so that if TAB|UNTAB is pressed, we | ||
| 203 | * move to curr_hot (current hottest line). | ||
| 204 | */ | ||
| 205 | if (nd != NULL && RB_EMPTY_NODE(nd)) | ||
| 206 | nd = NULL; | ||
| 207 | } | ||
| 208 | |||
| 209 | switch (key) { | ||
| 210 | case -1: | ||
| 211 | /* | ||
| 212 | * FIXME we need to check if it was | ||
| 213 | * es.reason == NEWT_EXIT_TIMER | ||
| 214 | */ | ||
| 215 | if (refresh != 0) | ||
| 216 | symbol__annotate_decay_histogram(sym, evidx); | ||
| 217 | continue; | ||
| 218 | case NEWT_KEY_TAB: | ||
| 219 | if (nd != NULL) { | ||
| 220 | nd = rb_prev(nd); | ||
| 221 | if (nd == NULL) | ||
| 222 | nd = rb_last(&self->entries); | ||
| 223 | } else | ||
| 224 | nd = self->curr_hot; | ||
| 225 | break; | ||
| 226 | case NEWT_KEY_UNTAB: | ||
| 227 | if (nd != NULL) | ||
| 228 | nd = rb_next(nd); | ||
| 229 | if (nd == NULL) | ||
| 230 | nd = rb_first(&self->entries); | ||
| 231 | else | ||
| 232 | nd = self->curr_hot; | ||
| 233 | break; | ||
| 234 | case 'H': | ||
| 235 | nd = self->curr_hot; | ||
| 236 | break; | ||
| 237 | default: | ||
| 238 | goto out; | ||
| 239 | } | ||
| 240 | |||
| 241 | if (nd != NULL) | ||
| 242 | annotate_browser__set_top(self, nd); | ||
| 243 | } | ||
| 244 | out: | ||
| 245 | ui_browser__hide(&self->b); | ||
| 246 | return key; | ||
| 247 | } | ||
| 248 | |||
| 249 | int hist_entry__tui_annotate(struct hist_entry *he, int evidx) | ||
| 250 | { | ||
| 251 | return symbol__tui_annotate(he->ms.sym, he->ms.map, evidx, 0); | ||
| 252 | } | ||
| 253 | |||
| 254 | int symbol__tui_annotate(struct symbol *sym, struct map *map, int evidx, | ||
| 255 | int refresh) | ||
| 256 | { | ||
| 257 | struct objdump_line *pos, *n; | ||
| 258 | struct annotation *notes; | ||
| 259 | struct annotate_browser browser = { | ||
| 260 | .b = { | ||
| 261 | .refresh = ui_browser__list_head_refresh, | ||
| 262 | .seek = ui_browser__list_head_seek, | ||
| 263 | .write = annotate_browser__write, | ||
| 264 | .priv = sym, | ||
| 265 | }, | ||
| 266 | }; | ||
| 267 | int ret; | ||
| 268 | |||
| 269 | if (sym == NULL) | ||
| 270 | return -1; | ||
| 271 | |||
| 272 | if (map->dso->annotate_warned) | ||
| 273 | return -1; | ||
| 274 | |||
| 275 | if (symbol__annotate(sym, map, sizeof(struct objdump_line_rb_node)) < 0) { | ||
| 276 | ui__error_window(ui_helpline__last_msg); | ||
| 277 | return -1; | ||
| 278 | } | ||
| 279 | |||
| 280 | ui_helpline__push("Press <- or ESC to exit"); | ||
| 281 | |||
| 282 | notes = symbol__annotation(sym); | ||
| 283 | |||
| 284 | list_for_each_entry(pos, ¬es->src->source, node) { | ||
| 285 | struct objdump_line_rb_node *rbpos; | ||
| 286 | size_t line_len = strlen(pos->line); | ||
| 287 | |||
| 288 | if (browser.b.width < line_len) | ||
| 289 | browser.b.width = line_len; | ||
| 290 | rbpos = objdump_line__rb(pos); | ||
| 291 | rbpos->idx = browser.b.nr_entries++; | ||
| 292 | } | ||
| 293 | |||
| 294 | browser.b.entries = ¬es->src->source, | ||
| 295 | browser.b.width += 18; /* Percentage */ | ||
| 296 | ret = annotate_browser__run(&browser, evidx, refresh); | ||
| 297 | list_for_each_entry_safe(pos, n, ¬es->src->source, node) { | ||
| 298 | list_del(&pos->node); | ||
| 299 | objdump_line__free(pos); | ||
| 300 | } | ||
| 301 | return ret; | ||
| 302 | } | ||
diff --git a/tools/perf/util/ui/browsers/hists.c b/tools/perf/util/ui/browsers/hists.c new file mode 100644 index 00000000000..5d767c622df --- /dev/null +++ b/tools/perf/util/ui/browsers/hists.c | |||
| @@ -0,0 +1,1138 @@ | |||
| 1 | #define _GNU_SOURCE | ||
| 2 | #include <stdio.h> | ||
| 3 | #undef _GNU_SOURCE | ||
| 4 | #include "../libslang.h" | ||
| 5 | #include <stdlib.h> | ||
| 6 | #include <string.h> | ||
| 7 | #include <newt.h> | ||
| 8 | #include <linux/rbtree.h> | ||
| 9 | |||
| 10 | #include "../../evsel.h" | ||
| 11 | #include "../../evlist.h" | ||
| 12 | #include "../../hist.h" | ||
| 13 | #include "../../pstack.h" | ||
| 14 | #include "../../sort.h" | ||
| 15 | #include "../../util.h" | ||
| 16 | |||
| 17 | #include "../browser.h" | ||
| 18 | #include "../helpline.h" | ||
| 19 | #include "../util.h" | ||
| 20 | #include "map.h" | ||
| 21 | |||
| 22 | struct hist_browser { | ||
| 23 | struct ui_browser b; | ||
| 24 | struct hists *hists; | ||
| 25 | struct hist_entry *he_selection; | ||
| 26 | struct map_symbol *selection; | ||
| 27 | }; | ||
| 28 | |||
| 29 | static void hist_browser__refresh_dimensions(struct hist_browser *self) | ||
| 30 | { | ||
| 31 | /* 3 == +/- toggle symbol before actual hist_entry rendering */ | ||
| 32 | self->b.width = 3 + (hists__sort_list_width(self->hists) + | ||
| 33 | sizeof("[k]")); | ||
| 34 | } | ||
| 35 | |||
| 36 | static void hist_browser__reset(struct hist_browser *self) | ||
| 37 | { | ||
| 38 | self->b.nr_entries = self->hists->nr_entries; | ||
| 39 | hist_browser__refresh_dimensions(self); | ||
| 40 | ui_browser__reset_index(&self->b); | ||
| 41 | } | ||
| 42 | |||
| 43 | static char tree__folded_sign(bool unfolded) | ||
| 44 | { | ||
| 45 | return unfolded ? '-' : '+'; | ||
| 46 | } | ||
| 47 | |||
| 48 | static char map_symbol__folded(const struct map_symbol *self) | ||
| 49 | { | ||
| 50 | return self->has_children ? tree__folded_sign(self->unfolded) : ' '; | ||
| 51 | } | ||
| 52 | |||
| 53 | static char hist_entry__folded(const struct hist_entry *self) | ||
| 54 | { | ||
| 55 | return map_symbol__folded(&self->ms); | ||
| 56 | } | ||
| 57 | |||
| 58 | static char callchain_list__folded(const struct callchain_list *self) | ||
| 59 | { | ||
| 60 | return map_symbol__folded(&self->ms); | ||
| 61 | } | ||
| 62 | |||
| 63 | static void map_symbol__set_folding(struct map_symbol *self, bool unfold) | ||
| 64 | { | ||
| 65 | self->unfolded = unfold ? self->has_children : false; | ||
| 66 | } | ||
| 67 | |||
| 68 | static int callchain_node__count_rows_rb_tree(struct callchain_node *self) | ||
| 69 | { | ||
| 70 | int n = 0; | ||
| 71 | struct rb_node *nd; | ||
| 72 | |||
| 73 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | ||
| 74 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | ||
| 75 | struct callchain_list *chain; | ||
| 76 | char folded_sign = ' '; /* No children */ | ||
| 77 | |||
| 78 | list_for_each_entry(chain, &child->val, list) { | ||
| 79 | ++n; | ||
| 80 | /* We need this because we may not have children */ | ||
| 81 | folded_sign = callchain_list__folded(chain); | ||
| 82 | if (folded_sign == '+') | ||
| 83 | break; | ||
| 84 | } | ||
| 85 | |||
| 86 | if (folded_sign == '-') /* Have children and they're unfolded */ | ||
| 87 | n += callchain_node__count_rows_rb_tree(child); | ||
| 88 | } | ||
| 89 | |||
| 90 | return n; | ||
| 91 | } | ||
| 92 | |||
| 93 | static int callchain_node__count_rows(struct callchain_node *node) | ||
| 94 | { | ||
| 95 | struct callchain_list *chain; | ||
| 96 | bool unfolded = false; | ||
| 97 | int n = 0; | ||
| 98 | |||
| 99 | list_for_each_entry(chain, &node->val, list) { | ||
| 100 | ++n; | ||
| 101 | unfolded = chain->ms.unfolded; | ||
| 102 | } | ||
| 103 | |||
| 104 | if (unfolded) | ||
| 105 | n += callchain_node__count_rows_rb_tree(node); | ||
| 106 | |||
| 107 | return n; | ||
| 108 | } | ||
| 109 | |||
| 110 | static int callchain__count_rows(struct rb_root *chain) | ||
| 111 | { | ||
| 112 | struct rb_node *nd; | ||
| 113 | int n = 0; | ||
| 114 | |||
| 115 | for (nd = rb_first(chain); nd; nd = rb_next(nd)) { | ||
| 116 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
| 117 | n += callchain_node__count_rows(node); | ||
| 118 | } | ||
| 119 | |||
| 120 | return n; | ||
| 121 | } | ||
| 122 | |||
| 123 | static bool map_symbol__toggle_fold(struct map_symbol *self) | ||
| 124 | { | ||
| 125 | if (!self->has_children) | ||
| 126 | return false; | ||
| 127 | |||
| 128 | self->unfolded = !self->unfolded; | ||
| 129 | return true; | ||
| 130 | } | ||
| 131 | |||
| 132 | static void callchain_node__init_have_children_rb_tree(struct callchain_node *self) | ||
| 133 | { | ||
| 134 | struct rb_node *nd = rb_first(&self->rb_root); | ||
| 135 | |||
| 136 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | ||
| 137 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | ||
| 138 | struct callchain_list *chain; | ||
| 139 | bool first = true; | ||
| 140 | |||
| 141 | list_for_each_entry(chain, &child->val, list) { | ||
| 142 | if (first) { | ||
| 143 | first = false; | ||
| 144 | chain->ms.has_children = chain->list.next != &child->val || | ||
| 145 | !RB_EMPTY_ROOT(&child->rb_root); | ||
| 146 | } else | ||
| 147 | chain->ms.has_children = chain->list.next == &child->val && | ||
| 148 | !RB_EMPTY_ROOT(&child->rb_root); | ||
| 149 | } | ||
| 150 | |||
| 151 | callchain_node__init_have_children_rb_tree(child); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | static void callchain_node__init_have_children(struct callchain_node *self) | ||
| 156 | { | ||
| 157 | struct callchain_list *chain; | ||
| 158 | |||
| 159 | list_for_each_entry(chain, &self->val, list) | ||
| 160 | chain->ms.has_children = !RB_EMPTY_ROOT(&self->rb_root); | ||
| 161 | |||
| 162 | callchain_node__init_have_children_rb_tree(self); | ||
| 163 | } | ||
| 164 | |||
| 165 | static void callchain__init_have_children(struct rb_root *self) | ||
| 166 | { | ||
| 167 | struct rb_node *nd; | ||
| 168 | |||
| 169 | for (nd = rb_first(self); nd; nd = rb_next(nd)) { | ||
| 170 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
| 171 | callchain_node__init_have_children(node); | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | static void hist_entry__init_have_children(struct hist_entry *self) | ||
| 176 | { | ||
| 177 | if (!self->init_have_children) { | ||
| 178 | self->ms.has_children = !RB_EMPTY_ROOT(&self->sorted_chain); | ||
| 179 | callchain__init_have_children(&self->sorted_chain); | ||
| 180 | self->init_have_children = true; | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | static bool hist_browser__toggle_fold(struct hist_browser *self) | ||
| 185 | { | ||
| 186 | if (map_symbol__toggle_fold(self->selection)) { | ||
| 187 | struct hist_entry *he = self->he_selection; | ||
| 188 | |||
| 189 | hist_entry__init_have_children(he); | ||
| 190 | self->hists->nr_entries -= he->nr_rows; | ||
| 191 | |||
| 192 | if (he->ms.unfolded) | ||
| 193 | he->nr_rows = callchain__count_rows(&he->sorted_chain); | ||
| 194 | else | ||
| 195 | he->nr_rows = 0; | ||
| 196 | self->hists->nr_entries += he->nr_rows; | ||
| 197 | self->b.nr_entries = self->hists->nr_entries; | ||
| 198 | |||
| 199 | return true; | ||
| 200 | } | ||
| 201 | |||
| 202 | /* If it doesn't have children, no toggling performed */ | ||
| 203 | return false; | ||
| 204 | } | ||
| 205 | |||
| 206 | static int callchain_node__set_folding_rb_tree(struct callchain_node *self, bool unfold) | ||
| 207 | { | ||
| 208 | int n = 0; | ||
| 209 | struct rb_node *nd; | ||
| 210 | |||
| 211 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | ||
| 212 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | ||
| 213 | struct callchain_list *chain; | ||
| 214 | bool has_children = false; | ||
| 215 | |||
| 216 | list_for_each_entry(chain, &child->val, list) { | ||
| 217 | ++n; | ||
| 218 | map_symbol__set_folding(&chain->ms, unfold); | ||
| 219 | has_children = chain->ms.has_children; | ||
| 220 | } | ||
| 221 | |||
| 222 | if (has_children) | ||
| 223 | n += callchain_node__set_folding_rb_tree(child, unfold); | ||
| 224 | } | ||
| 225 | |||
| 226 | return n; | ||
| 227 | } | ||
| 228 | |||
| 229 | static int callchain_node__set_folding(struct callchain_node *node, bool unfold) | ||
| 230 | { | ||
| 231 | struct callchain_list *chain; | ||
| 232 | bool has_children = false; | ||
| 233 | int n = 0; | ||
| 234 | |||
| 235 | list_for_each_entry(chain, &node->val, list) { | ||
| 236 | ++n; | ||
| 237 | map_symbol__set_folding(&chain->ms, unfold); | ||
| 238 | has_children = chain->ms.has_children; | ||
| 239 | } | ||
| 240 | |||
| 241 | if (has_children) | ||
| 242 | n += callchain_node__set_folding_rb_tree(node, unfold); | ||
| 243 | |||
| 244 | return n; | ||
| 245 | } | ||
| 246 | |||
| 247 | static int callchain__set_folding(struct rb_root *chain, bool unfold) | ||
| 248 | { | ||
| 249 | struct rb_node *nd; | ||
| 250 | int n = 0; | ||
| 251 | |||
| 252 | for (nd = rb_first(chain); nd; nd = rb_next(nd)) { | ||
| 253 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
| 254 | n += callchain_node__set_folding(node, unfold); | ||
| 255 | } | ||
| 256 | |||
| 257 | return n; | ||
| 258 | } | ||
| 259 | |||
| 260 | static void hist_entry__set_folding(struct hist_entry *self, bool unfold) | ||
| 261 | { | ||
| 262 | hist_entry__init_have_children(self); | ||
| 263 | map_symbol__set_folding(&self->ms, unfold); | ||
| 264 | |||
| 265 | if (self->ms.has_children) { | ||
| 266 | int n = callchain__set_folding(&self->sorted_chain, unfold); | ||
| 267 | self->nr_rows = unfold ? n : 0; | ||
| 268 | } else | ||
| 269 | self->nr_rows = 0; | ||
| 270 | } | ||
| 271 | |||
| 272 | static void hists__set_folding(struct hists *self, bool unfold) | ||
| 273 | { | ||
| 274 | struct rb_node *nd; | ||
| 275 | |||
| 276 | self->nr_entries = 0; | ||
| 277 | |||
| 278 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { | ||
| 279 | struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node); | ||
| 280 | hist_entry__set_folding(he, unfold); | ||
| 281 | self->nr_entries += 1 + he->nr_rows; | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | static void hist_browser__set_folding(struct hist_browser *self, bool unfold) | ||
| 286 | { | ||
| 287 | hists__set_folding(self->hists, unfold); | ||
| 288 | self->b.nr_entries = self->hists->nr_entries; | ||
| 289 | /* Go to the start, we may be way after valid entries after a collapse */ | ||
| 290 | ui_browser__reset_index(&self->b); | ||
| 291 | } | ||
| 292 | |||
| 293 | static int hist_browser__run(struct hist_browser *self, const char *title) | ||
| 294 | { | ||
| 295 | int key; | ||
| 296 | int exit_keys[] = { 'a', '?', 'h', 'C', 'd', 'D', 'E', 't', | ||
| 297 | NEWT_KEY_ENTER, NEWT_KEY_RIGHT, NEWT_KEY_LEFT, | ||
| 298 | NEWT_KEY_TAB, NEWT_KEY_UNTAB, 0, }; | ||
| 299 | |||
| 300 | self->b.entries = &self->hists->entries; | ||
| 301 | self->b.nr_entries = self->hists->nr_entries; | ||
| 302 | |||
| 303 | hist_browser__refresh_dimensions(self); | ||
| 304 | |||
| 305 | if (ui_browser__show(&self->b, title, | ||
| 306 | "Press '?' for help on key bindings") < 0) | ||
| 307 | return -1; | ||
| 308 | |||
| 309 | ui_browser__add_exit_keys(&self->b, exit_keys); | ||
| 310 | |||
| 311 | while (1) { | ||
| 312 | key = ui_browser__run(&self->b); | ||
| 313 | |||
| 314 | switch (key) { | ||
| 315 | case 'D': { /* Debug */ | ||
| 316 | static int seq; | ||
| 317 | struct hist_entry *h = rb_entry(self->b.top, | ||
| 318 | struct hist_entry, rb_node); | ||
| 319 | ui_helpline__pop(); | ||
| 320 | ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", | ||
| 321 | seq++, self->b.nr_entries, | ||
| 322 | self->hists->nr_entries, | ||
| 323 | self->b.height, | ||
| 324 | self->b.index, | ||
| 325 | self->b.top_idx, | ||
| 326 | h->row_offset, h->nr_rows); | ||
| 327 | } | ||
| 328 | break; | ||
| 329 | case 'C': | ||
| 330 | /* Collapse the whole world. */ | ||
| 331 | hist_browser__set_folding(self, false); | ||
| 332 | break; | ||
| 333 | case 'E': | ||
| 334 | /* Expand the whole world. */ | ||
| 335 | hist_browser__set_folding(self, true); | ||
| 336 | break; | ||
| 337 | case NEWT_KEY_ENTER: | ||
| 338 | if (hist_browser__toggle_fold(self)) | ||
| 339 | break; | ||
| 340 | /* fall thru */ | ||
| 341 | default: | ||
| 342 | goto out; | ||
| 343 | } | ||
| 344 | } | ||
| 345 | out: | ||
| 346 | ui_browser__hide(&self->b); | ||
| 347 | return key; | ||
| 348 | } | ||
| 349 | |||
| 350 | static char *callchain_list__sym_name(struct callchain_list *self, | ||
| 351 | char *bf, size_t bfsize) | ||
| 352 | { | ||
| 353 | if (self->ms.sym) | ||
| 354 | return self->ms.sym->name; | ||
| 355 | |||
| 356 | snprintf(bf, bfsize, "%#" PRIx64, self->ip); | ||
| 357 | return bf; | ||
| 358 | } | ||
| 359 | |||
| 360 | #define LEVEL_OFFSET_STEP 3 | ||
| 361 | |||
| 362 | static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self, | ||
| 363 | struct callchain_node *chain_node, | ||
| 364 | u64 total, int level, | ||
| 365 | unsigned short row, | ||
| 366 | off_t *row_offset, | ||
| 367 | bool *is_current_entry) | ||
| 368 | { | ||
| 369 | struct rb_node *node; | ||
| 370 | int first_row = row, width, offset = level * LEVEL_OFFSET_STEP; | ||
| 371 | u64 new_total, remaining; | ||
| 372 | |||
| 373 | if (callchain_param.mode == CHAIN_GRAPH_REL) | ||
| 374 | new_total = chain_node->children_hit; | ||
| 375 | else | ||
| 376 | new_total = total; | ||
| 377 | |||
| 378 | remaining = new_total; | ||
| 379 | node = rb_first(&chain_node->rb_root); | ||
| 380 | while (node) { | ||
| 381 | struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); | ||
| 382 | struct rb_node *next = rb_next(node); | ||
| 383 | u64 cumul = callchain_cumul_hits(child); | ||
| 384 | struct callchain_list *chain; | ||
| 385 | char folded_sign = ' '; | ||
| 386 | int first = true; | ||
| 387 | int extra_offset = 0; | ||
| 388 | |||
| 389 | remaining -= cumul; | ||
| 390 | |||
| 391 | list_for_each_entry(chain, &child->val, list) { | ||
| 392 | char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str; | ||
| 393 | const char *str; | ||
| 394 | int color; | ||
| 395 | bool was_first = first; | ||
| 396 | |||
| 397 | if (first) | ||
| 398 | first = false; | ||
| 399 | else | ||
| 400 | extra_offset = LEVEL_OFFSET_STEP; | ||
| 401 | |||
| 402 | folded_sign = callchain_list__folded(chain); | ||
| 403 | if (*row_offset != 0) { | ||
| 404 | --*row_offset; | ||
| 405 | goto do_next; | ||
| 406 | } | ||
| 407 | |||
| 408 | alloc_str = NULL; | ||
| 409 | str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | ||
| 410 | if (was_first) { | ||
| 411 | double percent = cumul * 100.0 / new_total; | ||
| 412 | |||
| 413 | if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) | ||
| 414 | str = "Not enough memory!"; | ||
| 415 | else | ||
| 416 | str = alloc_str; | ||
| 417 | } | ||
| 418 | |||
| 419 | color = HE_COLORSET_NORMAL; | ||
| 420 | width = self->b.width - (offset + extra_offset + 2); | ||
| 421 | if (ui_browser__is_current_entry(&self->b, row)) { | ||
| 422 | self->selection = &chain->ms; | ||
| 423 | color = HE_COLORSET_SELECTED; | ||
| 424 | *is_current_entry = true; | ||
| 425 | } | ||
| 426 | |||
| 427 | ui_browser__set_color(&self->b, color); | ||
| 428 | ui_browser__gotorc(&self->b, row, 0); | ||
| 429 | slsmg_write_nstring(" ", offset + extra_offset); | ||
| 430 | slsmg_printf("%c ", folded_sign); | ||
| 431 | slsmg_write_nstring(str, width); | ||
| 432 | free(alloc_str); | ||
| 433 | |||
| 434 | if (++row == self->b.height) | ||
| 435 | goto out; | ||
| 436 | do_next: | ||
| 437 | if (folded_sign == '+') | ||
| 438 | break; | ||
| 439 | } | ||
| 440 | |||
| 441 | if (folded_sign == '-') { | ||
| 442 | const int new_level = level + (extra_offset ? 2 : 1); | ||
| 443 | row += hist_browser__show_callchain_node_rb_tree(self, child, new_total, | ||
| 444 | new_level, row, row_offset, | ||
| 445 | is_current_entry); | ||
| 446 | } | ||
| 447 | if (row == self->b.height) | ||
| 448 | goto out; | ||
| 449 | node = next; | ||
| 450 | } | ||
| 451 | out: | ||
| 452 | return row - first_row; | ||
| 453 | } | ||
| 454 | |||
| 455 | static int hist_browser__show_callchain_node(struct hist_browser *self, | ||
| 456 | struct callchain_node *node, | ||
| 457 | int level, unsigned short row, | ||
| 458 | off_t *row_offset, | ||
| 459 | bool *is_current_entry) | ||
| 460 | { | ||
| 461 | struct callchain_list *chain; | ||
| 462 | int first_row = row, | ||
| 463 | offset = level * LEVEL_OFFSET_STEP, | ||
| 464 | width = self->b.width - offset; | ||
| 465 | char folded_sign = ' '; | ||
| 466 | |||
| 467 | list_for_each_entry(chain, &node->val, list) { | ||
| 468 | char ipstr[BITS_PER_LONG / 4 + 1], *s; | ||
| 469 | int color; | ||
| 470 | |||
| 471 | folded_sign = callchain_list__folded(chain); | ||
| 472 | |||
| 473 | if (*row_offset != 0) { | ||
| 474 | --*row_offset; | ||
| 475 | continue; | ||
| 476 | } | ||
| 477 | |||
| 478 | color = HE_COLORSET_NORMAL; | ||
| 479 | if (ui_browser__is_current_entry(&self->b, row)) { | ||
| 480 | self->selection = &chain->ms; | ||
| 481 | color = HE_COLORSET_SELECTED; | ||
| 482 | *is_current_entry = true; | ||
| 483 | } | ||
| 484 | |||
| 485 | s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | ||
| 486 | ui_browser__gotorc(&self->b, row, 0); | ||
| 487 | ui_browser__set_color(&self->b, color); | ||
| 488 | slsmg_write_nstring(" ", offset); | ||
| 489 | slsmg_printf("%c ", folded_sign); | ||
| 490 | slsmg_write_nstring(s, width - 2); | ||
| 491 | |||
| 492 | if (++row == self->b.height) | ||
| 493 | goto out; | ||
| 494 | } | ||
| 495 | |||
| 496 | if (folded_sign == '-') | ||
| 497 | row += hist_browser__show_callchain_node_rb_tree(self, node, | ||
| 498 | self->hists->stats.total_period, | ||
| 499 | level + 1, row, | ||
| 500 | row_offset, | ||
| 501 | is_current_entry); | ||
| 502 | out: | ||
| 503 | return row - first_row; | ||
| 504 | } | ||
| 505 | |||
| 506 | static int hist_browser__show_callchain(struct hist_browser *self, | ||
| 507 | struct rb_root *chain, | ||
| 508 | int level, unsigned short row, | ||
| 509 | off_t *row_offset, | ||
| 510 | bool *is_current_entry) | ||
| 511 | { | ||
| 512 | struct rb_node *nd; | ||
| 513 | int first_row = row; | ||
| 514 | |||
| 515 | for (nd = rb_first(chain); nd; nd = rb_next(nd)) { | ||
| 516 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
| 517 | |||
| 518 | row += hist_browser__show_callchain_node(self, node, level, | ||
| 519 | row, row_offset, | ||
| 520 | is_current_entry); | ||
| 521 | if (row == self->b.height) | ||
| 522 | break; | ||
| 523 | } | ||
| 524 | |||
| 525 | return row - first_row; | ||
| 526 | } | ||
| 527 | |||
| 528 | static int hist_browser__show_entry(struct hist_browser *self, | ||
| 529 | struct hist_entry *entry, | ||
| 530 | unsigned short row) | ||
| 531 | { | ||
| 532 | char s[256]; | ||
| 533 | double percent; | ||
| 534 | int printed = 0; | ||
| 535 | int color, width = self->b.width; | ||
| 536 | char folded_sign = ' '; | ||
| 537 | bool current_entry = ui_browser__is_current_entry(&self->b, row); | ||
| 538 | off_t row_offset = entry->row_offset; | ||
| 539 | |||
| 540 | if (current_entry) { | ||
| 541 | self->he_selection = entry; | ||
| 542 | self->selection = &entry->ms; | ||
| 543 | } | ||
| 544 | |||
| 545 | if (symbol_conf.use_callchain) { | ||
| 546 | hist_entry__init_have_children(entry); | ||
| 547 | folded_sign = hist_entry__folded(entry); | ||
| 548 | } | ||
| 549 | |||
| 550 | if (row_offset == 0) { | ||
| 551 | hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false, | ||
| 552 | 0, false, self->hists->stats.total_period); | ||
| 553 | percent = (entry->period * 100.0) / self->hists->stats.total_period; | ||
| 554 | |||
| 555 | color = HE_COLORSET_SELECTED; | ||
| 556 | if (!current_entry) { | ||
| 557 | if (percent >= MIN_RED) | ||
| 558 | color = HE_COLORSET_TOP; | ||
| 559 | else if (percent >= MIN_GREEN) | ||
| 560 | color = HE_COLORSET_MEDIUM; | ||
| 561 | else | ||
| 562 | color = HE_COLORSET_NORMAL; | ||
| 563 | } | ||
| 564 | |||
| 565 | ui_browser__set_color(&self->b, color); | ||
| 566 | ui_browser__gotorc(&self->b, row, 0); | ||
| 567 | if (symbol_conf.use_callchain) { | ||
| 568 | slsmg_printf("%c ", folded_sign); | ||
| 569 | width -= 2; | ||
| 570 | } | ||
| 571 | slsmg_write_nstring(s, width); | ||
| 572 | ++row; | ||
| 573 | ++printed; | ||
| 574 | } else | ||
| 575 | --row_offset; | ||
| 576 | |||
| 577 | if (folded_sign == '-' && row != self->b.height) { | ||
| 578 | printed += hist_browser__show_callchain(self, &entry->sorted_chain, | ||
| 579 | 1, row, &row_offset, | ||
| 580 | ¤t_entry); | ||
| 581 | if (current_entry) | ||
| 582 | self->he_selection = entry; | ||
| 583 | } | ||
| 584 | |||
| 585 | return printed; | ||
| 586 | } | ||
| 587 | |||
| 588 | static unsigned int hist_browser__refresh(struct ui_browser *self) | ||
| 589 | { | ||
| 590 | unsigned row = 0; | ||
| 591 | struct rb_node *nd; | ||
| 592 | struct hist_browser *hb = container_of(self, struct hist_browser, b); | ||
| 593 | |||
| 594 | if (self->top == NULL) | ||
| 595 | self->top = rb_first(&hb->hists->entries); | ||
| 596 | |||
| 597 | for (nd = self->top; nd; nd = rb_next(nd)) { | ||
| 598 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
| 599 | |||
| 600 | if (h->filtered) | ||
| 601 | continue; | ||
| 602 | |||
| 603 | row += hist_browser__show_entry(hb, h, row); | ||
| 604 | if (row == self->height) | ||
| 605 | break; | ||
| 606 | } | ||
| 607 | |||
| 608 | return row; | ||
| 609 | } | ||
| 610 | |||
| 611 | static struct rb_node *hists__filter_entries(struct rb_node *nd) | ||
| 612 | { | ||
| 613 | while (nd != NULL) { | ||
| 614 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
| 615 | if (!h->filtered) | ||
| 616 | return nd; | ||
| 617 | |||
| 618 | nd = rb_next(nd); | ||
| 619 | } | ||
| 620 | |||
| 621 | return NULL; | ||
| 622 | } | ||
| 623 | |||
| 624 | static struct rb_node *hists__filter_prev_entries(struct rb_node *nd) | ||
| 625 | { | ||
| 626 | while (nd != NULL) { | ||
| 627 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
| 628 | if (!h->filtered) | ||
| 629 | return nd; | ||
| 630 | |||
| 631 | nd = rb_prev(nd); | ||
| 632 | } | ||
| 633 | |||
| 634 | return NULL; | ||
| 635 | } | ||
| 636 | |||
| 637 | static void ui_browser__hists_seek(struct ui_browser *self, | ||
| 638 | off_t offset, int whence) | ||
| 639 | { | ||
| 640 | struct hist_entry *h; | ||
| 641 | struct rb_node *nd; | ||
| 642 | bool first = true; | ||
| 643 | |||
| 644 | if (self->nr_entries == 0) | ||
| 645 | return; | ||
| 646 | |||
| 647 | switch (whence) { | ||
| 648 | case SEEK_SET: | ||
| 649 | nd = hists__filter_entries(rb_first(self->entries)); | ||
| 650 | break; | ||
| 651 | case SEEK_CUR: | ||
| 652 | nd = self->top; | ||
| 653 | goto do_offset; | ||
| 654 | case SEEK_END: | ||
| 655 | nd = hists__filter_prev_entries(rb_last(self->entries)); | ||
| 656 | first = false; | ||
| 657 | break; | ||
| 658 | default: | ||
| 659 | return; | ||
| 660 | } | ||
| 661 | |||
| 662 | /* | ||
| 663 | * Moves not relative to the first visible entry invalidates its | ||
| 664 | * row_offset: | ||
| 665 | */ | ||
| 666 | h = rb_entry(self->top, struct hist_entry, rb_node); | ||
| 667 | h->row_offset = 0; | ||
| 668 | |||
| 669 | /* | ||
| 670 | * Here we have to check if nd is expanded (+), if it is we can't go | ||
| 671 | * the next top level hist_entry, instead we must compute an offset of | ||
| 672 | * what _not_ to show and not change the first visible entry. | ||
| 673 | * | ||
| 674 | * This offset increments when we are going from top to bottom and | ||
| 675 | * decreases when we're going from bottom to top. | ||
| 676 | * | ||
| 677 | * As we don't have backpointers to the top level in the callchains | ||
| 678 | * structure, we need to always print the whole hist_entry callchain, | ||
| 679 | * skipping the first ones that are before the first visible entry | ||
| 680 | * and stop when we printed enough lines to fill the screen. | ||
| 681 | */ | ||
| 682 | do_offset: | ||
| 683 | if (offset > 0) { | ||
| 684 | do { | ||
| 685 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
| 686 | if (h->ms.unfolded) { | ||
| 687 | u16 remaining = h->nr_rows - h->row_offset; | ||
| 688 | if (offset > remaining) { | ||
| 689 | offset -= remaining; | ||
| 690 | h->row_offset = 0; | ||
| 691 | } else { | ||
| 692 | h->row_offset += offset; | ||
| 693 | offset = 0; | ||
| 694 | self->top = nd; | ||
| 695 | break; | ||
| 696 | } | ||
| 697 | } | ||
| 698 | nd = hists__filter_entries(rb_next(nd)); | ||
| 699 | if (nd == NULL) | ||
| 700 | break; | ||
| 701 | --offset; | ||
| 702 | self->top = nd; | ||
| 703 | } while (offset != 0); | ||
| 704 | } else if (offset < 0) { | ||
| 705 | while (1) { | ||
| 706 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
| 707 | if (h->ms.unfolded) { | ||
| 708 | if (first) { | ||
| 709 | if (-offset > h->row_offset) { | ||
| 710 | offset += h->row_offset; | ||
| 711 | h->row_offset = 0; | ||
| 712 | } else { | ||
| 713 | h->row_offset += offset; | ||
| 714 | offset = 0; | ||
| 715 | self->top = nd; | ||
| 716 | break; | ||
| 717 | } | ||
| 718 | } else { | ||
| 719 | if (-offset > h->nr_rows) { | ||
| 720 | offset += h->nr_rows; | ||
| 721 | h->row_offset = 0; | ||
| 722 | } else { | ||
| 723 | h->row_offset = h->nr_rows + offset; | ||
| 724 | offset = 0; | ||
| 725 | self->top = nd; | ||
| 726 | break; | ||
| 727 | } | ||
| 728 | } | ||
| 729 | } | ||
| 730 | |||
| 731 | nd = hists__filter_prev_entries(rb_prev(nd)); | ||
| 732 | if (nd == NULL) | ||
| 733 | break; | ||
| 734 | ++offset; | ||
| 735 | self->top = nd; | ||
| 736 | if (offset == 0) { | ||
| 737 | /* | ||
| 738 | * Last unfiltered hist_entry, check if it is | ||
| 739 | * unfolded, if it is then we should have | ||
| 740 | * row_offset at its last entry. | ||
| 741 | */ | ||
| 742 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
| 743 | if (h->ms.unfolded) | ||
| 744 | h->row_offset = h->nr_rows; | ||
| 745 | break; | ||
| 746 | } | ||
| 747 | first = false; | ||
| 748 | } | ||
| 749 | } else { | ||
| 750 | self->top = nd; | ||
| 751 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
| 752 | h->row_offset = 0; | ||
| 753 | } | ||
| 754 | } | ||
| 755 | |||
| 756 | static struct hist_browser *hist_browser__new(struct hists *hists) | ||
| 757 | { | ||
| 758 | struct hist_browser *self = zalloc(sizeof(*self)); | ||
| 759 | |||
| 760 | if (self) { | ||
| 761 | self->hists = hists; | ||
| 762 | self->b.refresh = hist_browser__refresh; | ||
| 763 | self->b.seek = ui_browser__hists_seek; | ||
| 764 | } | ||
| 765 | |||
| 766 | return self; | ||
| 767 | } | ||
| 768 | |||
| 769 | static void hist_browser__delete(struct hist_browser *self) | ||
| 770 | { | ||
| 771 | free(self); | ||
| 772 | } | ||
| 773 | |||
| 774 | static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) | ||
| 775 | { | ||
| 776 | return self->he_selection; | ||
| 777 | } | ||
| 778 | |||
| 779 | static struct thread *hist_browser__selected_thread(struct hist_browser *self) | ||
| 780 | { | ||
| 781 | return self->he_selection->thread; | ||
| 782 | } | ||
| 783 | |||
| 784 | static int hists__browser_title(struct hists *self, char *bf, size_t size, | ||
| 785 | const char *ev_name, const struct dso *dso, | ||
| 786 | const struct thread *thread) | ||
| 787 | { | ||
| 788 | char unit; | ||
| 789 | int printed; | ||
| 790 | unsigned long nr_events = self->stats.nr_events[PERF_RECORD_SAMPLE]; | ||
| 791 | |||
| 792 | nr_events = convert_unit(nr_events, &unit); | ||
| 793 | printed = snprintf(bf, size, "Events: %lu%c %s", nr_events, unit, ev_name); | ||
| 794 | |||
| 795 | if (thread) | ||
| 796 | printed += snprintf(bf + printed, size - printed, | ||
| 797 | ", Thread: %s(%d)", | ||
| 798 | (thread->comm_set ? thread->comm : ""), | ||
| 799 | thread->pid); | ||
| 800 | if (dso) | ||
| 801 | printed += snprintf(bf + printed, size - printed, | ||
| 802 | ", DSO: %s", dso->short_name); | ||
| 803 | return printed; | ||
| 804 | } | ||
| 805 | |||
| 806 | static int perf_evsel__hists_browse(struct perf_evsel *evsel, | ||
| 807 | const char *helpline, const char *ev_name, | ||
| 808 | bool left_exits) | ||
| 809 | { | ||
| 810 | struct hists *self = &evsel->hists; | ||
| 811 | struct hist_browser *browser = hist_browser__new(self); | ||
| 812 | struct pstack *fstack; | ||
| 813 | const struct thread *thread_filter = NULL; | ||
| 814 | const struct dso *dso_filter = NULL; | ||
| 815 | char msg[160]; | ||
| 816 | int key = -1; | ||
| 817 | |||
| 818 | if (browser == NULL) | ||
| 819 | return -1; | ||
| 820 | |||
| 821 | fstack = pstack__new(2); | ||
| 822 | if (fstack == NULL) | ||
| 823 | goto out; | ||
| 824 | |||
| 825 | ui_helpline__push(helpline); | ||
| 826 | |||
| 827 | hists__browser_title(self, msg, sizeof(msg), ev_name, | ||
| 828 | dso_filter, thread_filter); | ||
| 829 | while (1) { | ||
| 830 | const struct thread *thread = NULL; | ||
| 831 | const struct dso *dso = NULL; | ||
| 832 | char *options[16]; | ||
| 833 | int nr_options = 0, choice = 0, i, | ||
| 834 | annotate = -2, zoom_dso = -2, zoom_thread = -2, | ||
| 835 | browse_map = -2; | ||
| 836 | |||
| 837 | key = hist_browser__run(browser, msg); | ||
| 838 | |||
| 839 | if (browser->he_selection != NULL) { | ||
| 840 | thread = hist_browser__selected_thread(browser); | ||
| 841 | dso = browser->selection->map ? browser->selection->map->dso : NULL; | ||
| 842 | } | ||
| 843 | |||
| 844 | switch (key) { | ||
| 845 | case NEWT_KEY_TAB: | ||
| 846 | case NEWT_KEY_UNTAB: | ||
| 847 | /* | ||
| 848 | * Exit the browser, let hists__browser_tree | ||
| 849 | * go to the next or previous | ||
| 850 | */ | ||
| 851 | goto out_free_stack; | ||
| 852 | case 'a': | ||
| 853 | if (browser->selection == NULL || | ||
| 854 | browser->selection->sym == NULL || | ||
| 855 | browser->selection->map->dso->annotate_warned) | ||
| 856 | continue; | ||
| 857 | goto do_annotate; | ||
| 858 | case 'd': | ||
| 859 | goto zoom_dso; | ||
| 860 | case 't': | ||
| 861 | goto zoom_thread; | ||
| 862 | case NEWT_KEY_F1: | ||
| 863 | case 'h': | ||
| 864 | case '?': | ||
| 865 | ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n" | ||
| 866 | "<- Zoom out\n" | ||
| 867 | "a Annotate current symbol\n" | ||
| 868 | "h/?/F1 Show this window\n" | ||
| 869 | "C Collapse all callchains\n" | ||
| 870 | "E Expand all callchains\n" | ||
| 871 | "d Zoom into current DSO\n" | ||
| 872 | "t Zoom into current Thread\n" | ||
| 873 | "TAB/UNTAB Switch events\n" | ||
| 874 | "q/CTRL+C Exit browser"); | ||
| 875 | continue; | ||
| 876 | case NEWT_KEY_ENTER: | ||
| 877 | case NEWT_KEY_RIGHT: | ||
| 878 | /* menu */ | ||
| 879 | break; | ||
| 880 | case NEWT_KEY_LEFT: { | ||
| 881 | const void *top; | ||
| 882 | |||
| 883 | if (pstack__empty(fstack)) { | ||
| 884 | /* | ||
| 885 | * Go back to the perf_evsel_menu__run or other user | ||
| 886 | */ | ||
| 887 | if (left_exits) | ||
| 888 | goto out_free_stack; | ||
| 889 | continue; | ||
| 890 | } | ||
| 891 | top = pstack__pop(fstack); | ||
| 892 | if (top == &dso_filter) | ||
| 893 | goto zoom_out_dso; | ||
| 894 | if (top == &thread_filter) | ||
| 895 | goto zoom_out_thread; | ||
| 896 | continue; | ||
| 897 | } | ||
| 898 | case NEWT_KEY_ESCAPE: | ||
| 899 | if (!left_exits && | ||
| 900 | !ui__dialog_yesno("Do you really want to exit?")) | ||
| 901 | continue; | ||
| 902 | /* Fall thru */ | ||
| 903 | default: | ||
| 904 | goto out_free_stack; | ||
| 905 | } | ||
| 906 | |||
| 907 | if (browser->selection != NULL && | ||
| 908 | browser->selection->sym != NULL && | ||
| 909 | !browser->selection->map->dso->annotate_warned && | ||
| 910 | asprintf(&options[nr_options], "Annotate %s", | ||
| 911 | browser->selection->sym->name) > 0) | ||
| 912 | annotate = nr_options++; | ||
| 913 | |||
| 914 | if (thread != NULL && | ||
| 915 | asprintf(&options[nr_options], "Zoom %s %s(%d) thread", | ||
| 916 | (thread_filter ? "out of" : "into"), | ||
| 917 | (thread->comm_set ? thread->comm : ""), | ||
| 918 | thread->pid) > 0) | ||
| 919 | zoom_thread = nr_options++; | ||
| 920 | |||
| 921 | if (dso != NULL && | ||
| 922 | asprintf(&options[nr_options], "Zoom %s %s DSO", | ||
| 923 | (dso_filter ? "out of" : "into"), | ||
| 924 | (dso->kernel ? "the Kernel" : dso->short_name)) > 0) | ||
| 925 | zoom_dso = nr_options++; | ||
| 926 | |||
| 927 | if (browser->selection != NULL && | ||
| 928 | browser->selection->map != NULL && | ||
| 929 | asprintf(&options[nr_options], "Browse map details") > 0) | ||
| 930 | browse_map = nr_options++; | ||
| 931 | |||
| 932 | options[nr_options++] = (char *)"Exit"; | ||
| 933 | |||
| 934 | choice = ui__popup_menu(nr_options, options); | ||
| 935 | |||
| 936 | for (i = 0; i < nr_options - 1; ++i) | ||
| 937 | free(options[i]); | ||
| 938 | |||
| 939 | if (choice == nr_options - 1) | ||
| 940 | break; | ||
| 941 | |||
| 942 | if (choice == -1) | ||
| 943 | continue; | ||
| 944 | |||
| 945 | if (choice == annotate) { | ||
| 946 | struct hist_entry *he; | ||
| 947 | do_annotate: | ||
| 948 | he = hist_browser__selected_entry(browser); | ||
| 949 | if (he == NULL) | ||
| 950 | continue; | ||
| 951 | |||
| 952 | hist_entry__tui_annotate(he, evsel->idx); | ||
| 953 | } else if (choice == browse_map) | ||
| 954 | map__browse(browser->selection->map); | ||
| 955 | else if (choice == zoom_dso) { | ||
| 956 | zoom_dso: | ||
| 957 | if (dso_filter) { | ||
| 958 | pstack__remove(fstack, &dso_filter); | ||
| 959 | zoom_out_dso: | ||
| 960 | ui_helpline__pop(); | ||
| 961 | dso_filter = NULL; | ||
| 962 | } else { | ||
| 963 | if (dso == NULL) | ||
| 964 | continue; | ||
| 965 | ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"", | ||
| 966 | dso->kernel ? "the Kernel" : dso->short_name); | ||
| 967 | dso_filter = dso; | ||
| 968 | pstack__push(fstack, &dso_filter); | ||
| 969 | } | ||
| 970 | hists__filter_by_dso(self, dso_filter); | ||
| 971 | hists__browser_title(self, msg, sizeof(msg), ev_name, | ||
| 972 | dso_filter, thread_filter); | ||
| 973 | hist_browser__reset(browser); | ||
| 974 | } else if (choice == zoom_thread) { | ||
| 975 | zoom_thread: | ||
| 976 | if (thread_filter) { | ||
| 977 | pstack__remove(fstack, &thread_filter); | ||
| 978 | zoom_out_thread: | ||
| 979 | ui_helpline__pop(); | ||
| 980 | thread_filter = NULL; | ||
| 981 | } else { | ||
| 982 | ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", | ||
| 983 | thread->comm_set ? thread->comm : "", | ||
| 984 | thread->pid); | ||
| 985 | thread_filter = thread; | ||
| 986 | pstack__push(fstack, &thread_filter); | ||
| 987 | } | ||
| 988 | hists__filter_by_thread(self, thread_filter); | ||
| 989 | hists__browser_title(self, msg, sizeof(msg), ev_name, | ||
| 990 | dso_filter, thread_filter); | ||
| 991 | hist_browser__reset(browser); | ||
| 992 | } | ||
| 993 | } | ||
| 994 | out_free_stack: | ||
| 995 | pstack__delete(fstack); | ||
| 996 | out: | ||
| 997 | hist_browser__delete(browser); | ||
| 998 | return key; | ||
| 999 | } | ||
| 1000 | |||
| 1001 | struct perf_evsel_menu { | ||
| 1002 | struct ui_browser b; | ||
| 1003 | struct perf_evsel *selection; | ||
| 1004 | }; | ||
| 1005 | |||
| 1006 | static void perf_evsel_menu__write(struct ui_browser *browser, | ||
| 1007 | void *entry, int row) | ||
| 1008 | { | ||
| 1009 | struct perf_evsel_menu *menu = container_of(browser, | ||
| 1010 | struct perf_evsel_menu, b); | ||
| 1011 | struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node); | ||
| 1012 | bool current_entry = ui_browser__is_current_entry(browser, row); | ||
| 1013 | unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE]; | ||
| 1014 | const char *ev_name = event_name(evsel); | ||
| 1015 | char bf[256], unit; | ||
| 1016 | |||
| 1017 | ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED : | ||
| 1018 | HE_COLORSET_NORMAL); | ||
| 1019 | |||
| 1020 | nr_events = convert_unit(nr_events, &unit); | ||
| 1021 | snprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events, | ||
| 1022 | unit, unit == ' ' ? "" : " ", ev_name); | ||
| 1023 | slsmg_write_nstring(bf, browser->width); | ||
| 1024 | |||
| 1025 | if (current_entry) | ||
| 1026 | menu->selection = evsel; | ||
| 1027 | } | ||
| 1028 | |||
| 1029 | static int perf_evsel_menu__run(struct perf_evsel_menu *menu, const char *help) | ||
| 1030 | { | ||
| 1031 | int exit_keys[] = { NEWT_KEY_ENTER, NEWT_KEY_RIGHT, 0, }; | ||
| 1032 | struct perf_evlist *evlist = menu->b.priv; | ||
| 1033 | struct perf_evsel *pos; | ||
| 1034 | const char *ev_name, *title = "Available samples"; | ||
| 1035 | int key; | ||
| 1036 | |||
| 1037 | if (ui_browser__show(&menu->b, title, | ||
| 1038 | "ESC: exit, ENTER|->: Browse histograms") < 0) | ||
| 1039 | return -1; | ||
| 1040 | |||
| 1041 | ui_browser__add_exit_keys(&menu->b, exit_keys); | ||
| 1042 | |||
| 1043 | while (1) { | ||
| 1044 | key = ui_browser__run(&menu->b); | ||
| 1045 | |||
| 1046 | switch (key) { | ||
| 1047 | case NEWT_KEY_RIGHT: | ||
| 1048 | case NEWT_KEY_ENTER: | ||
| 1049 | if (!menu->selection) | ||
| 1050 | continue; | ||
| 1051 | pos = menu->selection; | ||
| 1052 | browse_hists: | ||
| 1053 | ev_name = event_name(pos); | ||
| 1054 | key = perf_evsel__hists_browse(pos, help, ev_name, true); | ||
| 1055 | ui_browser__show_title(&menu->b, title); | ||
| 1056 | break; | ||
| 1057 | case NEWT_KEY_LEFT: | ||
| 1058 | continue; | ||
| 1059 | case NEWT_KEY_ESCAPE: | ||
| 1060 | if (!ui__dialog_yesno("Do you really want to exit?")) | ||
| 1061 | continue; | ||
| 1062 | /* Fall thru */ | ||
| 1063 | default: | ||
| 1064 | goto out; | ||
| 1065 | } | ||
| 1066 | |||
| 1067 | switch (key) { | ||
| 1068 | case NEWT_KEY_TAB: | ||
| 1069 | if (pos->node.next == &evlist->entries) | ||
| 1070 | pos = list_entry(evlist->entries.next, struct perf_evsel, node); | ||
| 1071 | else | ||
| 1072 | pos = list_entry(pos->node.next, struct perf_evsel, node); | ||
| 1073 | goto browse_hists; | ||
| 1074 | case NEWT_KEY_UNTAB: | ||
| 1075 | if (pos->node.prev == &evlist->entries) | ||
| 1076 | pos = list_entry(evlist->entries.prev, struct perf_evsel, node); | ||
| 1077 | else | ||
| 1078 | pos = list_entry(pos->node.prev, struct perf_evsel, node); | ||
| 1079 | goto browse_hists; | ||
| 1080 | case 'q': | ||
| 1081 | case CTRL('c'): | ||
| 1082 | goto out; | ||
| 1083 | default: | ||
| 1084 | break; | ||
| 1085 | } | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | out: | ||
| 1089 | ui_browser__hide(&menu->b); | ||
| 1090 | return key; | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist, | ||
| 1094 | const char *help) | ||
| 1095 | { | ||
| 1096 | struct perf_evsel *pos; | ||
| 1097 | struct perf_evsel_menu menu = { | ||
| 1098 | .b = { | ||
| 1099 | .entries = &evlist->entries, | ||
| 1100 | .refresh = ui_browser__list_head_refresh, | ||
| 1101 | .seek = ui_browser__list_head_seek, | ||
| 1102 | .write = perf_evsel_menu__write, | ||
| 1103 | .nr_entries = evlist->nr_entries, | ||
| 1104 | .priv = evlist, | ||
| 1105 | }, | ||
| 1106 | }; | ||
| 1107 | |||
| 1108 | ui_helpline__push("Press ESC to exit"); | ||
| 1109 | |||
| 1110 | list_for_each_entry(pos, &evlist->entries, node) { | ||
| 1111 | const char *ev_name = event_name(pos); | ||
| 1112 | size_t line_len = strlen(ev_name) + 7; | ||
| 1113 | |||
| 1114 | if (menu.b.width < line_len) | ||
| 1115 | menu.b.width = line_len; | ||
| 1116 | /* | ||
| 1117 | * Cache the evsel name, tracepoints have a _high_ cost per | ||
| 1118 | * event_name() call. | ||
| 1119 | */ | ||
| 1120 | if (pos->name == NULL) | ||
| 1121 | pos->name = strdup(ev_name); | ||
| 1122 | } | ||
| 1123 | |||
| 1124 | return perf_evsel_menu__run(&menu, help); | ||
| 1125 | } | ||
| 1126 | |||
| 1127 | int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help) | ||
| 1128 | { | ||
| 1129 | |||
| 1130 | if (evlist->nr_entries == 1) { | ||
| 1131 | struct perf_evsel *first = list_entry(evlist->entries.next, | ||
| 1132 | struct perf_evsel, node); | ||
| 1133 | const char *ev_name = event_name(first); | ||
| 1134 | return perf_evsel__hists_browse(first, help, ev_name, false); | ||
| 1135 | } | ||
| 1136 | |||
| 1137 | return __perf_evlist__tui_browse_hists(evlist, help); | ||
| 1138 | } | ||
diff --git a/tools/perf/util/ui/browsers/map.c b/tools/perf/util/ui/browsers/map.c new file mode 100644 index 00000000000..8462bffe20b --- /dev/null +++ b/tools/perf/util/ui/browsers/map.c | |||
| @@ -0,0 +1,156 @@ | |||
| 1 | #include "../libslang.h" | ||
| 2 | #include <elf.h> | ||
| 3 | #include <inttypes.h> | ||
| 4 | #include <sys/ttydefaults.h> | ||
| 5 | #include <ctype.h> | ||
| 6 | #include <string.h> | ||
| 7 | #include <linux/bitops.h> | ||
| 8 | #include "../../debug.h" | ||
| 9 | #include "../../symbol.h" | ||
| 10 | #include "../browser.h" | ||
| 11 | #include "../helpline.h" | ||
| 12 | #include "map.h" | ||
| 13 | |||
| 14 | static int ui_entry__read(const char *title, char *bf, size_t size, int width) | ||
| 15 | { | ||
| 16 | struct newtExitStruct es; | ||
| 17 | newtComponent form, entry; | ||
| 18 | const char *result; | ||
| 19 | int err = -1; | ||
| 20 | |||
| 21 | newtCenteredWindow(width, 1, title); | ||
| 22 | form = newtForm(NULL, NULL, 0); | ||
| 23 | if (form == NULL) | ||
| 24 | return -1; | ||
| 25 | |||
| 26 | entry = newtEntry(0, 0, "0x", width, &result, NEWT_FLAG_SCROLL); | ||
| 27 | if (entry == NULL) | ||
| 28 | goto out_free_form; | ||
| 29 | |||
| 30 | newtFormAddComponent(form, entry); | ||
| 31 | newtFormAddHotKey(form, NEWT_KEY_ENTER); | ||
| 32 | newtFormAddHotKey(form, NEWT_KEY_ESCAPE); | ||
| 33 | newtFormAddHotKey(form, NEWT_KEY_LEFT); | ||
| 34 | newtFormAddHotKey(form, CTRL('c')); | ||
| 35 | newtFormRun(form, &es); | ||
| 36 | |||
| 37 | if (result != NULL) { | ||
| 38 | strncpy(bf, result, size); | ||
| 39 | err = 0; | ||
| 40 | } | ||
| 41 | out_free_form: | ||
| 42 | newtPopWindow(); | ||
| 43 | newtFormDestroy(form); | ||
| 44 | return err; | ||
| 45 | } | ||
| 46 | |||
| 47 | struct map_browser { | ||
| 48 | struct ui_browser b; | ||
| 49 | struct map *map; | ||
| 50 | u8 addrlen; | ||
| 51 | }; | ||
| 52 | |||
| 53 | static void map_browser__write(struct ui_browser *self, void *nd, int row) | ||
| 54 | { | ||
| 55 | struct symbol *sym = rb_entry(nd, struct symbol, rb_node); | ||
| 56 | struct map_browser *mb = container_of(self, struct map_browser, b); | ||
| 57 | bool current_entry = ui_browser__is_current_entry(self, row); | ||
| 58 | int width; | ||
| 59 | |||
| 60 | ui_browser__set_percent_color(self, 0, current_entry); | ||
| 61 | slsmg_printf("%*" PRIx64 " %*" PRIx64 " %c ", | ||
| 62 | mb->addrlen, sym->start, mb->addrlen, sym->end, | ||
| 63 | sym->binding == STB_GLOBAL ? 'g' : | ||
| 64 | sym->binding == STB_LOCAL ? 'l' : 'w'); | ||
| 65 | width = self->width - ((mb->addrlen * 2) + 4); | ||
| 66 | if (width > 0) | ||
| 67 | slsmg_write_nstring(sym->name, width); | ||
| 68 | } | ||
| 69 | |||
| 70 | /* FIXME uber-kludgy, see comment on cmd_report... */ | ||
| 71 | static u32 *symbol__browser_index(struct symbol *self) | ||
| 72 | { | ||
| 73 | return ((void *)self) - sizeof(struct rb_node) - sizeof(u32); | ||
| 74 | } | ||
| 75 | |||
| 76 | static int map_browser__search(struct map_browser *self) | ||
| 77 | { | ||
| 78 | char target[512]; | ||
| 79 | struct symbol *sym; | ||
| 80 | int err = ui_entry__read("Search by name/addr", target, sizeof(target), 40); | ||
| 81 | |||
| 82 | if (err) | ||
| 83 | return err; | ||
| 84 | |||
| 85 | if (target[0] == '0' && tolower(target[1]) == 'x') { | ||
| 86 | u64 addr = strtoull(target, NULL, 16); | ||
| 87 | sym = map__find_symbol(self->map, addr, NULL); | ||
| 88 | } else | ||
| 89 | sym = map__find_symbol_by_name(self->map, target, NULL); | ||
| 90 | |||
| 91 | if (sym != NULL) { | ||
| 92 | u32 *idx = symbol__browser_index(sym); | ||
| 93 | |||
| 94 | self->b.top = &sym->rb_node; | ||
| 95 | self->b.index = self->b.top_idx = *idx; | ||
| 96 | } else | ||
| 97 | ui_helpline__fpush("%s not found!", target); | ||
| 98 | |||
| 99 | return 0; | ||
| 100 | } | ||
| 101 | |||
| 102 | static int map_browser__run(struct map_browser *self) | ||
| 103 | { | ||
| 104 | int key; | ||
| 105 | |||
| 106 | if (ui_browser__show(&self->b, self->map->dso->long_name, | ||
| 107 | "Press <- or ESC to exit, %s / to search", | ||
| 108 | verbose ? "" : "restart with -v to use") < 0) | ||
| 109 | return -1; | ||
| 110 | |||
| 111 | if (verbose) | ||
| 112 | ui_browser__add_exit_key(&self->b, '/'); | ||
| 113 | |||
| 114 | while (1) { | ||
| 115 | key = ui_browser__run(&self->b); | ||
| 116 | |||
| 117 | if (verbose && key == '/') | ||
| 118 | map_browser__search(self); | ||
| 119 | else | ||
| 120 | break; | ||
| 121 | } | ||
| 122 | |||
| 123 | ui_browser__hide(&self->b); | ||
| 124 | return key; | ||
| 125 | } | ||
| 126 | |||
| 127 | int map__browse(struct map *self) | ||
| 128 | { | ||
| 129 | struct map_browser mb = { | ||
| 130 | .b = { | ||
| 131 | .entries = &self->dso->symbols[self->type], | ||
| 132 | .refresh = ui_browser__rb_tree_refresh, | ||
| 133 | .seek = ui_browser__rb_tree_seek, | ||
| 134 | .write = map_browser__write, | ||
| 135 | }, | ||
| 136 | .map = self, | ||
| 137 | }; | ||
| 138 | struct rb_node *nd; | ||
| 139 | char tmp[BITS_PER_LONG / 4]; | ||
| 140 | u64 maxaddr = 0; | ||
| 141 | |||
| 142 | for (nd = rb_first(mb.b.entries); nd; nd = rb_next(nd)) { | ||
| 143 | struct symbol *pos = rb_entry(nd, struct symbol, rb_node); | ||
| 144 | |||
| 145 | if (maxaddr < pos->end) | ||
| 146 | maxaddr = pos->end; | ||
| 147 | if (verbose) { | ||
| 148 | u32 *idx = symbol__browser_index(pos); | ||
| 149 | *idx = mb.b.nr_entries; | ||
| 150 | } | ||
| 151 | ++mb.b.nr_entries; | ||
| 152 | } | ||
| 153 | |||
| 154 | mb.addrlen = snprintf(tmp, sizeof(tmp), "%" PRIx64, maxaddr); | ||
| 155 | return map_browser__run(&mb); | ||
| 156 | } | ||
diff --git a/tools/perf/util/ui/browsers/map.h b/tools/perf/util/ui/browsers/map.h new file mode 100644 index 00000000000..df8581a43e1 --- /dev/null +++ b/tools/perf/util/ui/browsers/map.h | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #ifndef _PERF_UI_MAP_BROWSER_H_ | ||
| 2 | #define _PERF_UI_MAP_BROWSER_H_ 1 | ||
| 3 | struct map; | ||
| 4 | |||
| 5 | int map__browse(struct map *self); | ||
| 6 | #endif /* _PERF_UI_MAP_BROWSER_H_ */ | ||
diff --git a/tools/perf/util/ui/browsers/top.c b/tools/perf/util/ui/browsers/top.c new file mode 100644 index 00000000000..88403cf8396 --- /dev/null +++ b/tools/perf/util/ui/browsers/top.c | |||
| @@ -0,0 +1,212 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com> | ||
| 3 | * | ||
| 4 | * Parts came from builtin-{top,stat,record}.c, see those files for further | ||
| 5 | * copyright notes. | ||
| 6 | * | ||
| 7 | * Released under the GPL v2. (and only v2, not any later version) | ||
| 8 | */ | ||
| 9 | #include "../browser.h" | ||
| 10 | #include "../../annotate.h" | ||
| 11 | #include "../helpline.h" | ||
| 12 | #include "../libslang.h" | ||
| 13 | #include "../util.h" | ||
| 14 | #include "../../evlist.h" | ||
| 15 | #include "../../hist.h" | ||
| 16 | #include "../../sort.h" | ||
| 17 | #include "../../symbol.h" | ||
| 18 | #include "../../top.h" | ||
| 19 | |||
| 20 | struct perf_top_browser { | ||
| 21 | struct ui_browser b; | ||
| 22 | struct rb_root root; | ||
| 23 | struct sym_entry *selection; | ||
| 24 | float sum_ksamples; | ||
| 25 | int dso_width; | ||
| 26 | int dso_short_width; | ||
| 27 | int sym_width; | ||
| 28 | }; | ||
| 29 | |||
| 30 | static void perf_top_browser__write(struct ui_browser *browser, void *entry, int row) | ||
| 31 | { | ||
| 32 | struct perf_top_browser *top_browser = container_of(browser, struct perf_top_browser, b); | ||
| 33 | struct sym_entry *syme = rb_entry(entry, struct sym_entry, rb_node); | ||
| 34 | bool current_entry = ui_browser__is_current_entry(browser, row); | ||
| 35 | struct symbol *symbol = sym_entry__symbol(syme); | ||
| 36 | struct perf_top *top = browser->priv; | ||
| 37 | int width = browser->width; | ||
| 38 | double pcnt; | ||
| 39 | |||
| 40 | pcnt = 100.0 - (100.0 * ((top_browser->sum_ksamples - syme->snap_count) / | ||
| 41 | top_browser->sum_ksamples)); | ||
| 42 | ui_browser__set_percent_color(browser, pcnt, current_entry); | ||
| 43 | |||
| 44 | if (top->evlist->nr_entries == 1 || !top->display_weighted) { | ||
| 45 | slsmg_printf("%20.2f ", syme->weight); | ||
| 46 | width -= 24; | ||
| 47 | } else { | ||
| 48 | slsmg_printf("%9.1f %10ld ", syme->weight, syme->snap_count); | ||
| 49 | width -= 23; | ||
| 50 | } | ||
| 51 | |||
| 52 | slsmg_printf("%4.1f%%", pcnt); | ||
| 53 | width -= 7; | ||
| 54 | |||
| 55 | if (verbose) { | ||
| 56 | slsmg_printf(" %016" PRIx64, symbol->start); | ||
| 57 | width -= 17; | ||
| 58 | } | ||
| 59 | |||
| 60 | slsmg_printf(" %-*.*s ", top_browser->sym_width, top_browser->sym_width, | ||
| 61 | symbol->name); | ||
| 62 | width -= top_browser->sym_width; | ||
| 63 | slsmg_write_nstring(width >= syme->map->dso->long_name_len ? | ||
| 64 | syme->map->dso->long_name : | ||
| 65 | syme->map->dso->short_name, width); | ||
| 66 | |||
| 67 | if (current_entry) | ||
| 68 | top_browser->selection = syme; | ||
| 69 | } | ||
| 70 | |||
| 71 | static void perf_top_browser__update_rb_tree(struct perf_top_browser *browser) | ||
| 72 | { | ||
| 73 | struct perf_top *top = browser->b.priv; | ||
| 74 | u64 top_idx = browser->b.top_idx; | ||
| 75 | |||
| 76 | browser->root = RB_ROOT; | ||
| 77 | browser->b.top = NULL; | ||
| 78 | browser->sum_ksamples = perf_top__decay_samples(top, &browser->root); | ||
| 79 | /* | ||
| 80 | * No active symbols | ||
| 81 | */ | ||
| 82 | if (top->rb_entries == 0) | ||
| 83 | return; | ||
| 84 | |||
| 85 | perf_top__find_widths(top, &browser->root, &browser->dso_width, | ||
| 86 | &browser->dso_short_width, | ||
| 87 | &browser->sym_width); | ||
| 88 | if (browser->sym_width + browser->dso_width > browser->b.width - 29) { | ||
| 89 | browser->dso_width = browser->dso_short_width; | ||
| 90 | if (browser->sym_width + browser->dso_width > browser->b.width - 29) | ||
| 91 | browser->sym_width = browser->b.width - browser->dso_width - 29; | ||
| 92 | } | ||
| 93 | |||
| 94 | /* | ||
| 95 | * Adjust the ui_browser indexes since the entries in the browser->root | ||
| 96 | * rb_tree may have changed, then seek it from start, so that we get a | ||
| 97 | * possible new top of the screen. | ||
| 98 | */ | ||
| 99 | browser->b.nr_entries = top->rb_entries; | ||
| 100 | |||
| 101 | if (top_idx >= browser->b.nr_entries) { | ||
| 102 | if (browser->b.height >= browser->b.nr_entries) | ||
| 103 | top_idx = browser->b.nr_entries - browser->b.height; | ||
| 104 | else | ||
| 105 | top_idx = 0; | ||
| 106 | } | ||
| 107 | |||
| 108 | if (browser->b.index >= top_idx + browser->b.height) | ||
| 109 | browser->b.index = top_idx + browser->b.index - browser->b.top_idx; | ||
| 110 | |||
| 111 | if (browser->b.index >= browser->b.nr_entries) | ||
| 112 | browser->b.index = browser->b.nr_entries - 1; | ||
| 113 | |||
| 114 | browser->b.top_idx = top_idx; | ||
| 115 | browser->b.seek(&browser->b, top_idx, SEEK_SET); | ||
| 116 | } | ||
| 117 | |||
| 118 | static void perf_top_browser__annotate(struct perf_top_browser *browser) | ||
| 119 | { | ||
| 120 | struct sym_entry *syme = browser->selection; | ||
| 121 | struct symbol *sym = sym_entry__symbol(syme); | ||
| 122 | struct annotation *notes = symbol__annotation(sym); | ||
| 123 | struct perf_top *top = browser->b.priv; | ||
| 124 | |||
| 125 | if (notes->src != NULL) | ||
| 126 | goto do_annotation; | ||
| 127 | |||
| 128 | pthread_mutex_lock(¬es->lock); | ||
| 129 | |||
| 130 | top->sym_filter_entry = NULL; | ||
| 131 | |||
| 132 | if (symbol__alloc_hist(sym, top->evlist->nr_entries) < 0) { | ||
| 133 | pr_err("Not enough memory for annotating '%s' symbol!\n", | ||
| 134 | sym->name); | ||
| 135 | pthread_mutex_unlock(¬es->lock); | ||
| 136 | return; | ||
| 137 | } | ||
| 138 | |||
| 139 | top->sym_filter_entry = syme; | ||
| 140 | |||
| 141 | pthread_mutex_unlock(¬es->lock); | ||
| 142 | do_annotation: | ||
| 143 | symbol__tui_annotate(sym, syme->map, 0, top->delay_secs * 1000); | ||
| 144 | } | ||
| 145 | |||
| 146 | static int perf_top_browser__run(struct perf_top_browser *browser) | ||
| 147 | { | ||
| 148 | int key; | ||
| 149 | char title[160]; | ||
| 150 | struct perf_top *top = browser->b.priv; | ||
| 151 | int delay_msecs = top->delay_secs * 1000; | ||
| 152 | int exit_keys[] = { 'a', NEWT_KEY_ENTER, NEWT_KEY_RIGHT, 0, }; | ||
| 153 | |||
| 154 | perf_top_browser__update_rb_tree(browser); | ||
| 155 | perf_top__header_snprintf(top, title, sizeof(title)); | ||
| 156 | perf_top__reset_sample_counters(top); | ||
| 157 | |||
| 158 | if (ui_browser__show(&browser->b, title, | ||
| 159 | "ESC: exit, ENTER|->|a: Live Annotate") < 0) | ||
| 160 | return -1; | ||
| 161 | |||
| 162 | newtFormSetTimer(browser->b.form, delay_msecs); | ||
| 163 | ui_browser__add_exit_keys(&browser->b, exit_keys); | ||
| 164 | |||
| 165 | while (1) { | ||
| 166 | key = ui_browser__run(&browser->b); | ||
| 167 | |||
| 168 | switch (key) { | ||
| 169 | case -1: | ||
| 170 | /* FIXME we need to check if it was es.reason == NEWT_EXIT_TIMER */ | ||
| 171 | perf_top_browser__update_rb_tree(browser); | ||
| 172 | perf_top__header_snprintf(top, title, sizeof(title)); | ||
| 173 | perf_top__reset_sample_counters(top); | ||
| 174 | ui_browser__set_color(&browser->b, NEWT_COLORSET_ROOT); | ||
| 175 | SLsmg_gotorc(0, 0); | ||
| 176 | slsmg_write_nstring(title, browser->b.width); | ||
| 177 | break; | ||
| 178 | case 'a': | ||
| 179 | case NEWT_KEY_RIGHT: | ||
| 180 | case NEWT_KEY_ENTER: | ||
| 181 | if (browser->selection) | ||
| 182 | perf_top_browser__annotate(browser); | ||
| 183 | break; | ||
| 184 | case NEWT_KEY_LEFT: | ||
| 185 | continue; | ||
| 186 | case NEWT_KEY_ESCAPE: | ||
| 187 | if (!ui__dialog_yesno("Do you really want to exit?")) | ||
| 188 | continue; | ||
| 189 | /* Fall thru */ | ||
| 190 | default: | ||
| 191 | goto out; | ||
| 192 | } | ||
| 193 | } | ||
| 194 | out: | ||
| 195 | ui_browser__hide(&browser->b); | ||
| 196 | return key; | ||
| 197 | } | ||
| 198 | |||
| 199 | int perf_top__tui_browser(struct perf_top *top) | ||
| 200 | { | ||
| 201 | struct perf_top_browser browser = { | ||
| 202 | .b = { | ||
| 203 | .entries = &browser.root, | ||
| 204 | .refresh = ui_browser__rb_tree_refresh, | ||
| 205 | .seek = ui_browser__rb_tree_seek, | ||
| 206 | .write = perf_top_browser__write, | ||
| 207 | .priv = top, | ||
| 208 | }, | ||
| 209 | }; | ||
| 210 | |||
| 211 | return perf_top_browser__run(&browser); | ||
| 212 | } | ||
diff --git a/tools/perf/util/ui/helpline.c b/tools/perf/util/ui/helpline.c new file mode 100644 index 00000000000..f36d2ff509e --- /dev/null +++ b/tools/perf/util/ui/helpline.c | |||
| @@ -0,0 +1,72 @@ | |||
| 1 | #define _GNU_SOURCE | ||
| 2 | #include <stdio.h> | ||
| 3 | #include <stdlib.h> | ||
| 4 | #include <newt.h> | ||
| 5 | |||
| 6 | #include "../debug.h" | ||
| 7 | #include "helpline.h" | ||
| 8 | #include "ui.h" | ||
| 9 | |||
| 10 | void ui_helpline__pop(void) | ||
| 11 | { | ||
| 12 | newtPopHelpLine(); | ||
| 13 | } | ||
| 14 | |||
| 15 | void ui_helpline__push(const char *msg) | ||
| 16 | { | ||
| 17 | newtPushHelpLine(msg); | ||
| 18 | } | ||
| 19 | |||
| 20 | void ui_helpline__vpush(const char *fmt, va_list ap) | ||
| 21 | { | ||
| 22 | char *s; | ||
| 23 | |||
| 24 | if (vasprintf(&s, fmt, ap) < 0) | ||
| 25 | vfprintf(stderr, fmt, ap); | ||
| 26 | else { | ||
| 27 | ui_helpline__push(s); | ||
| 28 | free(s); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | void ui_helpline__fpush(const char *fmt, ...) | ||
| 33 | { | ||
| 34 | va_list ap; | ||
| 35 | |||
| 36 | va_start(ap, fmt); | ||
| 37 | ui_helpline__vpush(fmt, ap); | ||
| 38 | va_end(ap); | ||
| 39 | } | ||
| 40 | |||
| 41 | void ui_helpline__puts(const char *msg) | ||
| 42 | { | ||
| 43 | ui_helpline__pop(); | ||
| 44 | ui_helpline__push(msg); | ||
| 45 | } | ||
| 46 | |||
| 47 | void ui_helpline__init(void) | ||
| 48 | { | ||
| 49 | ui_helpline__puts(" "); | ||
| 50 | } | ||
| 51 | |||
| 52 | char ui_helpline__last_msg[1024]; | ||
| 53 | |||
| 54 | int ui_helpline__show_help(const char *format, va_list ap) | ||
| 55 | { | ||
| 56 | int ret; | ||
| 57 | static int backlog; | ||
| 58 | |||
| 59 | pthread_mutex_lock(&ui__lock); | ||
| 60 | ret = vsnprintf(ui_helpline__last_msg + backlog, | ||
| 61 | sizeof(ui_helpline__last_msg) - backlog, format, ap); | ||
| 62 | backlog += ret; | ||
| 63 | |||
| 64 | if (ui_helpline__last_msg[backlog - 1] == '\n') { | ||
| 65 | ui_helpline__puts(ui_helpline__last_msg); | ||
| 66 | newtRefresh(); | ||
| 67 | backlog = 0; | ||
| 68 | } | ||
| 69 | pthread_mutex_unlock(&ui__lock); | ||
| 70 | |||
| 71 | return ret; | ||
| 72 | } | ||
diff --git a/tools/perf/util/ui/helpline.h b/tools/perf/util/ui/helpline.h new file mode 100644 index 00000000000..ab6028d0c40 --- /dev/null +++ b/tools/perf/util/ui/helpline.h | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | #ifndef _PERF_UI_HELPLINE_H_ | ||
| 2 | #define _PERF_UI_HELPLINE_H_ 1 | ||
| 3 | |||
| 4 | void ui_helpline__init(void); | ||
| 5 | void ui_helpline__pop(void); | ||
| 6 | void ui_helpline__push(const char *msg); | ||
| 7 | void ui_helpline__vpush(const char *fmt, va_list ap); | ||
| 8 | void ui_helpline__fpush(const char *fmt, ...); | ||
| 9 | void ui_helpline__puts(const char *msg); | ||
| 10 | |||
| 11 | #endif /* _PERF_UI_HELPLINE_H_ */ | ||
diff --git a/tools/perf/util/ui/libslang.h b/tools/perf/util/ui/libslang.h new file mode 100644 index 00000000000..2b63e1c9b18 --- /dev/null +++ b/tools/perf/util/ui/libslang.h | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | #ifndef _PERF_UI_SLANG_H_ | ||
| 2 | #define _PERF_UI_SLANG_H_ 1 | ||
| 3 | /* | ||
| 4 | * slang versions <= 2.0.6 have a "#if HAVE_LONG_LONG" that breaks | ||
| 5 | * the build if it isn't defined. Use the equivalent one that glibc | ||
| 6 | * has on features.h. | ||
| 7 | */ | ||
| 8 | #include <features.h> | ||
| 9 | #ifndef HAVE_LONG_LONG | ||
| 10 | #define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG | ||
| 11 | #endif | ||
| 12 | #include <slang.h> | ||
| 13 | |||
| 14 | #if SLANG_VERSION < 20104 | ||
| 15 | #define slsmg_printf(msg, args...) \ | ||
| 16 | SLsmg_printf((char *)(msg), ##args) | ||
| 17 | #define slsmg_write_nstring(msg, len) \ | ||
| 18 | SLsmg_write_nstring((char *)(msg), len) | ||
| 19 | #define sltt_set_color(obj, name, fg, bg) \ | ||
| 20 | SLtt_set_color(obj,(char *)(name), (char *)(fg), (char *)(bg)) | ||
| 21 | #else | ||
| 22 | #define slsmg_printf SLsmg_printf | ||
| 23 | #define slsmg_write_nstring SLsmg_write_nstring | ||
| 24 | #define sltt_set_color SLtt_set_color | ||
| 25 | #endif | ||
| 26 | |||
| 27 | #endif /* _PERF_UI_SLANG_H_ */ | ||
diff --git a/tools/perf/util/ui/progress.c b/tools/perf/util/ui/progress.c new file mode 100644 index 00000000000..d7fc399d36b --- /dev/null +++ b/tools/perf/util/ui/progress.c | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | #include <stdlib.h> | ||
| 2 | #include <newt.h> | ||
| 3 | #include "../cache.h" | ||
| 4 | #include "progress.h" | ||
| 5 | |||
| 6 | struct ui_progress { | ||
| 7 | newtComponent form, scale; | ||
| 8 | }; | ||
| 9 | |||
| 10 | struct ui_progress *ui_progress__new(const char *title, u64 total) | ||
| 11 | { | ||
| 12 | struct ui_progress *self = malloc(sizeof(*self)); | ||
| 13 | |||
| 14 | if (self != NULL) { | ||
| 15 | int cols; | ||
| 16 | |||
| 17 | if (use_browser <= 0) | ||
| 18 | return self; | ||
| 19 | newtGetScreenSize(&cols, NULL); | ||
| 20 | cols -= 4; | ||
| 21 | newtCenteredWindow(cols, 1, title); | ||
| 22 | self->form = newtForm(NULL, NULL, 0); | ||
| 23 | if (self->form == NULL) | ||
| 24 | goto out_free_self; | ||
| 25 | self->scale = newtScale(0, 0, cols, total); | ||
| 26 | if (self->scale == NULL) | ||
| 27 | goto out_free_form; | ||
| 28 | newtFormAddComponent(self->form, self->scale); | ||
| 29 | newtRefresh(); | ||
| 30 | } | ||
| 31 | |||
| 32 | return self; | ||
| 33 | |||
| 34 | out_free_form: | ||
| 35 | newtFormDestroy(self->form); | ||
| 36 | out_free_self: | ||
| 37 | free(self); | ||
| 38 | return NULL; | ||
| 39 | } | ||
| 40 | |||
| 41 | void ui_progress__update(struct ui_progress *self, u64 curr) | ||
| 42 | { | ||
| 43 | /* | ||
| 44 | * FIXME: We should have a per UI backend way of showing progress, | ||
| 45 | * stdio will just show a percentage as NN%, etc. | ||
| 46 | */ | ||
| 47 | if (use_browser <= 0) | ||
| 48 | return; | ||
| 49 | newtScaleSet(self->scale, curr); | ||
| 50 | newtRefresh(); | ||
| 51 | } | ||
| 52 | |||
| 53 | void ui_progress__delete(struct ui_progress *self) | ||
| 54 | { | ||
| 55 | if (use_browser > 0) { | ||
| 56 | newtFormDestroy(self->form); | ||
| 57 | newtPopWindow(); | ||
| 58 | } | ||
| 59 | free(self); | ||
| 60 | } | ||
diff --git a/tools/perf/util/ui/progress.h b/tools/perf/util/ui/progress.h new file mode 100644 index 00000000000..a3820a0beb5 --- /dev/null +++ b/tools/perf/util/ui/progress.h | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | #ifndef _PERF_UI_PROGRESS_H_ | ||
| 2 | #define _PERF_UI_PROGRESS_H_ 1 | ||
| 3 | |||
| 4 | struct ui_progress; | ||
| 5 | |||
| 6 | struct ui_progress *ui_progress__new(const char *title, u64 total); | ||
| 7 | void ui_progress__delete(struct ui_progress *self); | ||
| 8 | |||
| 9 | void ui_progress__update(struct ui_progress *self, u64 curr); | ||
| 10 | |||
| 11 | #endif | ||
diff --git a/tools/perf/util/ui/setup.c b/tools/perf/util/ui/setup.c new file mode 100644 index 00000000000..ee46d671db5 --- /dev/null +++ b/tools/perf/util/ui/setup.c | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | #include <newt.h> | ||
| 2 | #include <signal.h> | ||
| 3 | #include <stdbool.h> | ||
| 4 | |||
| 5 | #include "../cache.h" | ||
| 6 | #include "../debug.h" | ||
| 7 | #include "browser.h" | ||
| 8 | #include "helpline.h" | ||
| 9 | #include "ui.h" | ||
| 10 | |||
| 11 | pthread_mutex_t ui__lock = PTHREAD_MUTEX_INITIALIZER; | ||
| 12 | |||
| 13 | static void newt_suspend(void *d __used) | ||
| 14 | { | ||
| 15 | newtSuspend(); | ||
| 16 | raise(SIGTSTP); | ||
| 17 | newtResume(); | ||
| 18 | } | ||
| 19 | |||
| 20 | void setup_browser(bool fallback_to_pager) | ||
| 21 | { | ||
| 22 | if (!isatty(1) || !use_browser || dump_trace) { | ||
| 23 | use_browser = 0; | ||
| 24 | if (fallback_to_pager) | ||
| 25 | setup_pager(); | ||
| 26 | return; | ||
| 27 | } | ||
| 28 | |||
| 29 | use_browser = 1; | ||
| 30 | newtInit(); | ||
| 31 | newtCls(); | ||
| 32 | newtSetSuspendCallback(newt_suspend, NULL); | ||
| 33 | ui_helpline__init(); | ||
| 34 | ui_browser__init(); | ||
| 35 | } | ||
| 36 | |||
| 37 | void exit_browser(bool wait_for_ok) | ||
| 38 | { | ||
| 39 | if (use_browser > 0) { | ||
| 40 | if (wait_for_ok) { | ||
| 41 | char title[] = "Fatal Error", ok[] = "Ok"; | ||
| 42 | newtWinMessage(title, ok, ui_helpline__last_msg); | ||
| 43 | } | ||
| 44 | newtFinished(); | ||
| 45 | } | ||
| 46 | } | ||
diff --git a/tools/perf/util/ui/ui.h b/tools/perf/util/ui/ui.h new file mode 100644 index 00000000000..d264e059c82 --- /dev/null +++ b/tools/perf/util/ui/ui.h | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | #ifndef _PERF_UI_H_ | ||
| 2 | #define _PERF_UI_H_ 1 | ||
| 3 | |||
| 4 | #include <pthread.h> | ||
| 5 | |||
| 6 | extern pthread_mutex_t ui__lock; | ||
| 7 | |||
| 8 | #endif /* _PERF_UI_H_ */ | ||
diff --git a/tools/perf/util/ui/util.c b/tools/perf/util/ui/util.c new file mode 100644 index 00000000000..fdf1fc8f08b --- /dev/null +++ b/tools/perf/util/ui/util.c | |||
| @@ -0,0 +1,130 @@ | |||
| 1 | #include <newt.h> | ||
| 2 | #include <signal.h> | ||
| 3 | #include <stdio.h> | ||
| 4 | #include <stdbool.h> | ||
| 5 | #include <string.h> | ||
| 6 | #include <sys/ttydefaults.h> | ||
| 7 | |||
| 8 | #include "../cache.h" | ||
| 9 | #include "../debug.h" | ||
| 10 | #include "browser.h" | ||
| 11 | #include "helpline.h" | ||
| 12 | #include "ui.h" | ||
| 13 | #include "util.h" | ||
| 14 | |||
| 15 | static void newt_form__set_exit_keys(newtComponent self) | ||
| 16 | { | ||
| 17 | newtFormAddHotKey(self, NEWT_KEY_LEFT); | ||
| 18 | newtFormAddHotKey(self, NEWT_KEY_ESCAPE); | ||
| 19 | newtFormAddHotKey(self, 'Q'); | ||
| 20 | newtFormAddHotKey(self, 'q'); | ||
| 21 | newtFormAddHotKey(self, CTRL('c')); | ||
| 22 | } | ||
| 23 | |||
| 24 | static newtComponent newt_form__new(void) | ||
| 25 | { | ||
| 26 | newtComponent self = newtForm(NULL, NULL, 0); | ||
| 27 | if (self) | ||
| 28 | newt_form__set_exit_keys(self); | ||
| 29 | return self; | ||
| 30 | } | ||
| 31 | |||
| 32 | int ui__popup_menu(int argc, char * const argv[]) | ||
| 33 | { | ||
| 34 | struct newtExitStruct es; | ||
| 35 | int i, rc = -1, max_len = 5; | ||
| 36 | newtComponent listbox, form = newt_form__new(); | ||
| 37 | |||
| 38 | if (form == NULL) | ||
| 39 | return -1; | ||
| 40 | |||
| 41 | listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT); | ||
| 42 | if (listbox == NULL) | ||
| 43 | goto out_destroy_form; | ||
| 44 | |||
| 45 | newtFormAddComponent(form, listbox); | ||
| 46 | |||
| 47 | for (i = 0; i < argc; ++i) { | ||
| 48 | int len = strlen(argv[i]); | ||
| 49 | if (len > max_len) | ||
| 50 | max_len = len; | ||
| 51 | if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i)) | ||
| 52 | goto out_destroy_form; | ||
| 53 | } | ||
| 54 | |||
| 55 | newtCenteredWindow(max_len, argc, NULL); | ||
| 56 | newtFormRun(form, &es); | ||
| 57 | rc = newtListboxGetCurrent(listbox) - NULL; | ||
| 58 | if (es.reason == NEWT_EXIT_HOTKEY) | ||
| 59 | rc = -1; | ||
| 60 | newtPopWindow(); | ||
| 61 | out_destroy_form: | ||
| 62 | newtFormDestroy(form); | ||
| 63 | return rc; | ||
| 64 | } | ||
| 65 | |||
| 66 | int ui__help_window(const char *text) | ||
| 67 | { | ||
| 68 | struct newtExitStruct es; | ||
| 69 | newtComponent tb, form = newt_form__new(); | ||
| 70 | int rc = -1; | ||
| 71 | int max_len = 0, nr_lines = 0; | ||
| 72 | const char *t; | ||
| 73 | |||
| 74 | if (form == NULL) | ||
| 75 | return -1; | ||
| 76 | |||
| 77 | t = text; | ||
| 78 | while (1) { | ||
| 79 | const char *sep = strchr(t, '\n'); | ||
| 80 | int len; | ||
| 81 | |||
| 82 | if (sep == NULL) | ||
| 83 | sep = strchr(t, '\0'); | ||
| 84 | len = sep - t; | ||
| 85 | if (max_len < len) | ||
| 86 | max_len = len; | ||
| 87 | ++nr_lines; | ||
| 88 | if (*sep == '\0') | ||
| 89 | break; | ||
| 90 | t = sep + 1; | ||
| 91 | } | ||
| 92 | |||
| 93 | tb = newtTextbox(0, 0, max_len, nr_lines, 0); | ||
| 94 | if (tb == NULL) | ||
| 95 | goto out_destroy_form; | ||
| 96 | |||
| 97 | newtTextboxSetText(tb, text); | ||
| 98 | newtFormAddComponent(form, tb); | ||
| 99 | newtCenteredWindow(max_len, nr_lines, NULL); | ||
| 100 | newtFormRun(form, &es); | ||
| 101 | newtPopWindow(); | ||
| 102 | rc = 0; | ||
| 103 | out_destroy_form: | ||
| 104 | newtFormDestroy(form); | ||
| 105 | return rc; | ||
| 106 | } | ||
| 107 | |||
| 108 | static const char yes[] = "Yes", no[] = "No", | ||
| 109 | warning_str[] = "Warning!", ok[] = "Ok"; | ||
| 110 | |||
| 111 | bool ui__dialog_yesno(const char *msg) | ||
| 112 | { | ||
| 113 | /* newtWinChoice should really be accepting const char pointers... */ | ||
| 114 | return newtWinChoice(NULL, (char *)yes, (char *)no, (char *)msg) == 1; | ||
| 115 | } | ||
| 116 | |||
| 117 | void ui__warning(const char *format, ...) | ||
| 118 | { | ||
| 119 | va_list args; | ||
| 120 | |||
| 121 | va_start(args, format); | ||
| 122 | if (use_browser > 0) { | ||
| 123 | pthread_mutex_lock(&ui__lock); | ||
| 124 | newtWinMessagev((char *)warning_str, (char *)ok, | ||
| 125 | (char *)format, args); | ||
| 126 | pthread_mutex_unlock(&ui__lock); | ||
| 127 | } else | ||
| 128 | vfprintf(stderr, format, args); | ||
| 129 | va_end(args); | ||
| 130 | } | ||
diff --git a/tools/perf/util/ui/util.h b/tools/perf/util/ui/util.h new file mode 100644 index 00000000000..afcbc1d9953 --- /dev/null +++ b/tools/perf/util/ui/util.h | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | #ifndef _PERF_UI_UTIL_H_ | ||
| 2 | #define _PERF_UI_UTIL_H_ 1 | ||
| 3 | |||
| 4 | #include <stdbool.h> | ||
| 5 | |||
| 6 | int ui__popup_menu(int argc, char * const argv[]); | ||
| 7 | int ui__help_window(const char *text); | ||
| 8 | bool ui__dialog_yesno(const char *msg); | ||
| 9 | |||
| 10 | #endif /* _PERF_UI_UTIL_H_ */ | ||
