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_ */ | ||