diff options
author | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
---|---|---|
committer | Jonathan Herman <hermanjl@cs.unc.edu> | 2013-01-22 10:38:37 -0500 |
commit | fcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch) | |
tree | a57612d1888735a2ec7972891b68c1ac5ec8faea /tools | |
parent | 8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff) |
Diffstat (limited to 'tools')
-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 | ||||
-rw-r--r-- | tools/slub/slabinfo.c | 1385 |
19 files changed, 4907 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_ */ | ||
diff --git a/tools/slub/slabinfo.c b/tools/slub/slabinfo.c new file mode 100644 index 00000000000..868cc93f7ac --- /dev/null +++ b/tools/slub/slabinfo.c | |||
@@ -0,0 +1,1385 @@ | |||
1 | /* | ||
2 | * Slabinfo: Tool to get reports about slabs | ||
3 | * | ||
4 | * (C) 2007 sgi, Christoph Lameter | ||
5 | * (C) 2011 Linux Foundation, Christoph Lameter | ||
6 | * | ||
7 | * Compile with: | ||
8 | * | ||
9 | * gcc -o slabinfo slabinfo.c | ||
10 | */ | ||
11 | #include <stdio.h> | ||
12 | #include <stdlib.h> | ||
13 | #include <sys/types.h> | ||
14 | #include <dirent.h> | ||
15 | #include <strings.h> | ||
16 | #include <string.h> | ||
17 | #include <unistd.h> | ||
18 | #include <stdarg.h> | ||
19 | #include <getopt.h> | ||
20 | #include <regex.h> | ||
21 | #include <errno.h> | ||
22 | |||
23 | #define MAX_SLABS 500 | ||
24 | #define MAX_ALIASES 500 | ||
25 | #define MAX_NODES 1024 | ||
26 | |||
27 | struct slabinfo { | ||
28 | char *name; | ||
29 | int alias; | ||
30 | int refs; | ||
31 | int aliases, align, cache_dma, cpu_slabs, destroy_by_rcu; | ||
32 | int hwcache_align, object_size, objs_per_slab; | ||
33 | int sanity_checks, slab_size, store_user, trace; | ||
34 | int order, poison, reclaim_account, red_zone; | ||
35 | unsigned long partial, objects, slabs, objects_partial, objects_total; | ||
36 | unsigned long alloc_fastpath, alloc_slowpath; | ||
37 | unsigned long free_fastpath, free_slowpath; | ||
38 | unsigned long free_frozen, free_add_partial, free_remove_partial; | ||
39 | unsigned long alloc_from_partial, alloc_slab, free_slab, alloc_refill; | ||
40 | unsigned long cpuslab_flush, deactivate_full, deactivate_empty; | ||
41 | unsigned long deactivate_to_head, deactivate_to_tail; | ||
42 | unsigned long deactivate_remote_frees, order_fallback; | ||
43 | unsigned long cmpxchg_double_cpu_fail, cmpxchg_double_fail; | ||
44 | unsigned long alloc_node_mismatch, deactivate_bypass; | ||
45 | int numa[MAX_NODES]; | ||
46 | int numa_partial[MAX_NODES]; | ||
47 | } slabinfo[MAX_SLABS]; | ||
48 | |||
49 | struct aliasinfo { | ||
50 | char *name; | ||
51 | char *ref; | ||
52 | struct slabinfo *slab; | ||
53 | } aliasinfo[MAX_ALIASES]; | ||
54 | |||
55 | int slabs = 0; | ||
56 | int actual_slabs = 0; | ||
57 | int aliases = 0; | ||
58 | int alias_targets = 0; | ||
59 | int highest_node = 0; | ||
60 | |||
61 | char buffer[4096]; | ||
62 | |||
63 | int show_empty = 0; | ||
64 | int show_report = 0; | ||
65 | int show_alias = 0; | ||
66 | int show_slab = 0; | ||
67 | int skip_zero = 1; | ||
68 | int show_numa = 0; | ||
69 | int show_track = 0; | ||
70 | int show_first_alias = 0; | ||
71 | int validate = 0; | ||
72 | int shrink = 0; | ||
73 | int show_inverted = 0; | ||
74 | int show_single_ref = 0; | ||
75 | int show_totals = 0; | ||
76 | int sort_size = 0; | ||
77 | int sort_active = 0; | ||
78 | int set_debug = 0; | ||
79 | int show_ops = 0; | ||
80 | int show_activity = 0; | ||
81 | |||
82 | /* Debug options */ | ||
83 | int sanity = 0; | ||
84 | int redzone = 0; | ||
85 | int poison = 0; | ||
86 | int tracking = 0; | ||
87 | int tracing = 0; | ||
88 | |||
89 | int page_size; | ||
90 | |||
91 | regex_t pattern; | ||
92 | |||
93 | static void fatal(const char *x, ...) | ||
94 | { | ||
95 | va_list ap; | ||
96 | |||
97 | va_start(ap, x); | ||
98 | vfprintf(stderr, x, ap); | ||
99 | va_end(ap); | ||
100 | exit(EXIT_FAILURE); | ||
101 | } | ||
102 | |||
103 | static void usage(void) | ||
104 | { | ||
105 | printf("slabinfo 4/15/2011. (c) 2007 sgi/(c) 2011 Linux Foundation.\n\n" | ||
106 | "slabinfo [-ahnpvtsz] [-d debugopts] [slab-regexp]\n" | ||
107 | "-a|--aliases Show aliases\n" | ||
108 | "-A|--activity Most active slabs first\n" | ||
109 | "-d<options>|--debug=<options> Set/Clear Debug options\n" | ||
110 | "-D|--display-active Switch line format to activity\n" | ||
111 | "-e|--empty Show empty slabs\n" | ||
112 | "-f|--first-alias Show first alias\n" | ||
113 | "-h|--help Show usage information\n" | ||
114 | "-i|--inverted Inverted list\n" | ||
115 | "-l|--slabs Show slabs\n" | ||
116 | "-n|--numa Show NUMA information\n" | ||
117 | "-o|--ops Show kmem_cache_ops\n" | ||
118 | "-s|--shrink Shrink slabs\n" | ||
119 | "-r|--report Detailed report on single slabs\n" | ||
120 | "-S|--Size Sort by size\n" | ||
121 | "-t|--tracking Show alloc/free information\n" | ||
122 | "-T|--Totals Show summary information\n" | ||
123 | "-v|--validate Validate slabs\n" | ||
124 | "-z|--zero Include empty slabs\n" | ||
125 | "-1|--1ref Single reference\n" | ||
126 | "\nValid debug options (FZPUT may be combined)\n" | ||
127 | "a / A Switch on all debug options (=FZUP)\n" | ||
128 | "- Switch off all debug options\n" | ||
129 | "f / F Sanity Checks (SLAB_DEBUG_FREE)\n" | ||
130 | "z / Z Redzoning\n" | ||
131 | "p / P Poisoning\n" | ||
132 | "u / U Tracking\n" | ||
133 | "t / T Tracing\n" | ||
134 | ); | ||
135 | } | ||
136 | |||
137 | static unsigned long read_obj(const char *name) | ||
138 | { | ||
139 | FILE *f = fopen(name, "r"); | ||
140 | |||
141 | if (!f) | ||
142 | buffer[0] = 0; | ||
143 | else { | ||
144 | if (!fgets(buffer, sizeof(buffer), f)) | ||
145 | buffer[0] = 0; | ||
146 | fclose(f); | ||
147 | if (buffer[strlen(buffer)] == '\n') | ||
148 | buffer[strlen(buffer)] = 0; | ||
149 | } | ||
150 | return strlen(buffer); | ||
151 | } | ||
152 | |||
153 | |||
154 | /* | ||
155 | * Get the contents of an attribute | ||
156 | */ | ||
157 | static unsigned long get_obj(const char *name) | ||
158 | { | ||
159 | if (!read_obj(name)) | ||
160 | return 0; | ||
161 | |||
162 | return atol(buffer); | ||
163 | } | ||
164 | |||
165 | static unsigned long get_obj_and_str(const char *name, char **x) | ||
166 | { | ||
167 | unsigned long result = 0; | ||
168 | char *p; | ||
169 | |||
170 | *x = NULL; | ||
171 | |||
172 | if (!read_obj(name)) { | ||
173 | x = NULL; | ||
174 | return 0; | ||
175 | } | ||
176 | result = strtoul(buffer, &p, 10); | ||
177 | while (*p == ' ') | ||
178 | p++; | ||
179 | if (*p) | ||
180 | *x = strdup(p); | ||
181 | return result; | ||
182 | } | ||
183 | |||
184 | static void set_obj(struct slabinfo *s, const char *name, int n) | ||
185 | { | ||
186 | char x[100]; | ||
187 | FILE *f; | ||
188 | |||
189 | snprintf(x, 100, "%s/%s", s->name, name); | ||
190 | f = fopen(x, "w"); | ||
191 | if (!f) | ||
192 | fatal("Cannot write to %s\n", x); | ||
193 | |||
194 | fprintf(f, "%d\n", n); | ||
195 | fclose(f); | ||
196 | } | ||
197 | |||
198 | static unsigned long read_slab_obj(struct slabinfo *s, const char *name) | ||
199 | { | ||
200 | char x[100]; | ||
201 | FILE *f; | ||
202 | size_t l; | ||
203 | |||
204 | snprintf(x, 100, "%s/%s", s->name, name); | ||
205 | f = fopen(x, "r"); | ||
206 | if (!f) { | ||
207 | buffer[0] = 0; | ||
208 | l = 0; | ||
209 | } else { | ||
210 | l = fread(buffer, 1, sizeof(buffer), f); | ||
211 | buffer[l] = 0; | ||
212 | fclose(f); | ||
213 | } | ||
214 | return l; | ||
215 | } | ||
216 | |||
217 | |||
218 | /* | ||
219 | * Put a size string together | ||
220 | */ | ||
221 | static int store_size(char *buffer, unsigned long value) | ||
222 | { | ||
223 | unsigned long divisor = 1; | ||
224 | char trailer = 0; | ||
225 | int n; | ||
226 | |||
227 | if (value > 1000000000UL) { | ||
228 | divisor = 100000000UL; | ||
229 | trailer = 'G'; | ||
230 | } else if (value > 1000000UL) { | ||
231 | divisor = 100000UL; | ||
232 | trailer = 'M'; | ||
233 | } else if (value > 1000UL) { | ||
234 | divisor = 100; | ||
235 | trailer = 'K'; | ||
236 | } | ||
237 | |||
238 | value /= divisor; | ||
239 | n = sprintf(buffer, "%ld",value); | ||
240 | if (trailer) { | ||
241 | buffer[n] = trailer; | ||
242 | n++; | ||
243 | buffer[n] = 0; | ||
244 | } | ||
245 | if (divisor != 1) { | ||
246 | memmove(buffer + n - 2, buffer + n - 3, 4); | ||
247 | buffer[n-2] = '.'; | ||
248 | n++; | ||
249 | } | ||
250 | return n; | ||
251 | } | ||
252 | |||
253 | static void decode_numa_list(int *numa, char *t) | ||
254 | { | ||
255 | int node; | ||
256 | int nr; | ||
257 | |||
258 | memset(numa, 0, MAX_NODES * sizeof(int)); | ||
259 | |||
260 | if (!t) | ||
261 | return; | ||
262 | |||
263 | while (*t == 'N') { | ||
264 | t++; | ||
265 | node = strtoul(t, &t, 10); | ||
266 | if (*t == '=') { | ||
267 | t++; | ||
268 | nr = strtoul(t, &t, 10); | ||
269 | numa[node] = nr; | ||
270 | if (node > highest_node) | ||
271 | highest_node = node; | ||
272 | } | ||
273 | while (*t == ' ') | ||
274 | t++; | ||
275 | } | ||
276 | } | ||
277 | |||
278 | static void slab_validate(struct slabinfo *s) | ||
279 | { | ||
280 | if (strcmp(s->name, "*") == 0) | ||
281 | return; | ||
282 | |||
283 | set_obj(s, "validate", 1); | ||
284 | } | ||
285 | |||
286 | static void slab_shrink(struct slabinfo *s) | ||
287 | { | ||
288 | if (strcmp(s->name, "*") == 0) | ||
289 | return; | ||
290 | |||
291 | set_obj(s, "shrink", 1); | ||
292 | } | ||
293 | |||
294 | int line = 0; | ||
295 | |||
296 | static void first_line(void) | ||
297 | { | ||
298 | if (show_activity) | ||
299 | printf("Name Objects Alloc Free %%Fast Fallb O CmpX UL\n"); | ||
300 | else | ||
301 | printf("Name Objects Objsize Space " | ||
302 | "Slabs/Part/Cpu O/S O %%Fr %%Ef Flg\n"); | ||
303 | } | ||
304 | |||
305 | /* | ||
306 | * Find the shortest alias of a slab | ||
307 | */ | ||
308 | static struct aliasinfo *find_one_alias(struct slabinfo *find) | ||
309 | { | ||
310 | struct aliasinfo *a; | ||
311 | struct aliasinfo *best = NULL; | ||
312 | |||
313 | for(a = aliasinfo;a < aliasinfo + aliases; a++) { | ||
314 | if (a->slab == find && | ||
315 | (!best || strlen(best->name) < strlen(a->name))) { | ||
316 | best = a; | ||
317 | if (strncmp(a->name,"kmall", 5) == 0) | ||
318 | return best; | ||
319 | } | ||
320 | } | ||
321 | return best; | ||
322 | } | ||
323 | |||
324 | static unsigned long slab_size(struct slabinfo *s) | ||
325 | { | ||
326 | return s->slabs * (page_size << s->order); | ||
327 | } | ||
328 | |||
329 | static unsigned long slab_activity(struct slabinfo *s) | ||
330 | { | ||
331 | return s->alloc_fastpath + s->free_fastpath + | ||
332 | s->alloc_slowpath + s->free_slowpath; | ||
333 | } | ||
334 | |||
335 | static void slab_numa(struct slabinfo *s, int mode) | ||
336 | { | ||
337 | int node; | ||
338 | |||
339 | if (strcmp(s->name, "*") == 0) | ||
340 | return; | ||
341 | |||
342 | if (!highest_node) { | ||
343 | printf("\n%s: No NUMA information available.\n", s->name); | ||
344 | return; | ||
345 | } | ||
346 | |||
347 | if (skip_zero && !s->slabs) | ||
348 | return; | ||
349 | |||
350 | if (!line) { | ||
351 | printf("\n%-21s:", mode ? "NUMA nodes" : "Slab"); | ||
352 | for(node = 0; node <= highest_node; node++) | ||
353 | printf(" %4d", node); | ||
354 | printf("\n----------------------"); | ||
355 | for(node = 0; node <= highest_node; node++) | ||
356 | printf("-----"); | ||
357 | printf("\n"); | ||
358 | } | ||
359 | printf("%-21s ", mode ? "All slabs" : s->name); | ||
360 | for(node = 0; node <= highest_node; node++) { | ||
361 | char b[20]; | ||
362 | |||
363 | store_size(b, s->numa[node]); | ||
364 | printf(" %4s", b); | ||
365 | } | ||
366 | printf("\n"); | ||
367 | if (mode) { | ||
368 | printf("%-21s ", "Partial slabs"); | ||
369 | for(node = 0; node <= highest_node; node++) { | ||
370 | char b[20]; | ||
371 | |||
372 | store_size(b, s->numa_partial[node]); | ||
373 | printf(" %4s", b); | ||
374 | } | ||
375 | printf("\n"); | ||
376 | } | ||
377 | line++; | ||
378 | } | ||
379 | |||
380 | static void show_tracking(struct slabinfo *s) | ||
381 | { | ||
382 | printf("\n%s: Kernel object allocation\n", s->name); | ||
383 | printf("-----------------------------------------------------------------------\n"); | ||
384 | if (read_slab_obj(s, "alloc_calls")) | ||
385 | printf("%s", buffer); | ||
386 | else | ||
387 | printf("No Data\n"); | ||
388 | |||
389 | printf("\n%s: Kernel object freeing\n", s->name); | ||
390 | printf("------------------------------------------------------------------------\n"); | ||
391 | if (read_slab_obj(s, "free_calls")) | ||
392 | printf("%s", buffer); | ||
393 | else | ||
394 | printf("No Data\n"); | ||
395 | |||
396 | } | ||
397 | |||
398 | static void ops(struct slabinfo *s) | ||
399 | { | ||
400 | if (strcmp(s->name, "*") == 0) | ||
401 | return; | ||
402 | |||
403 | if (read_slab_obj(s, "ops")) { | ||
404 | printf("\n%s: kmem_cache operations\n", s->name); | ||
405 | printf("--------------------------------------------\n"); | ||
406 | printf("%s", buffer); | ||
407 | } else | ||
408 | printf("\n%s has no kmem_cache operations\n", s->name); | ||
409 | } | ||
410 | |||
411 | static const char *onoff(int x) | ||
412 | { | ||
413 | if (x) | ||
414 | return "On "; | ||
415 | return "Off"; | ||
416 | } | ||
417 | |||
418 | static void slab_stats(struct slabinfo *s) | ||
419 | { | ||
420 | unsigned long total_alloc; | ||
421 | unsigned long total_free; | ||
422 | unsigned long total; | ||
423 | |||
424 | if (!s->alloc_slab) | ||
425 | return; | ||
426 | |||
427 | total_alloc = s->alloc_fastpath + s->alloc_slowpath; | ||
428 | total_free = s->free_fastpath + s->free_slowpath; | ||
429 | |||
430 | if (!total_alloc) | ||
431 | return; | ||
432 | |||
433 | printf("\n"); | ||
434 | printf("Slab Perf Counter Alloc Free %%Al %%Fr\n"); | ||
435 | printf("--------------------------------------------------\n"); | ||
436 | printf("Fastpath %8lu %8lu %3lu %3lu\n", | ||
437 | s->alloc_fastpath, s->free_fastpath, | ||
438 | s->alloc_fastpath * 100 / total_alloc, | ||
439 | s->free_fastpath * 100 / total_free); | ||
440 | printf("Slowpath %8lu %8lu %3lu %3lu\n", | ||
441 | total_alloc - s->alloc_fastpath, s->free_slowpath, | ||
442 | (total_alloc - s->alloc_fastpath) * 100 / total_alloc, | ||
443 | s->free_slowpath * 100 / total_free); | ||
444 | printf("Page Alloc %8lu %8lu %3lu %3lu\n", | ||
445 | s->alloc_slab, s->free_slab, | ||
446 | s->alloc_slab * 100 / total_alloc, | ||
447 | s->free_slab * 100 / total_free); | ||
448 | printf("Add partial %8lu %8lu %3lu %3lu\n", | ||
449 | s->deactivate_to_head + s->deactivate_to_tail, | ||
450 | s->free_add_partial, | ||
451 | (s->deactivate_to_head + s->deactivate_to_tail) * 100 / total_alloc, | ||
452 | s->free_add_partial * 100 / total_free); | ||
453 | printf("Remove partial %8lu %8lu %3lu %3lu\n", | ||
454 | s->alloc_from_partial, s->free_remove_partial, | ||
455 | s->alloc_from_partial * 100 / total_alloc, | ||
456 | s->free_remove_partial * 100 / total_free); | ||
457 | |||
458 | printf("RemoteObj/SlabFrozen %8lu %8lu %3lu %3lu\n", | ||
459 | s->deactivate_remote_frees, s->free_frozen, | ||
460 | s->deactivate_remote_frees * 100 / total_alloc, | ||
461 | s->free_frozen * 100 / total_free); | ||
462 | |||
463 | printf("Total %8lu %8lu\n\n", total_alloc, total_free); | ||
464 | |||
465 | if (s->cpuslab_flush) | ||
466 | printf("Flushes %8lu\n", s->cpuslab_flush); | ||
467 | |||
468 | total = s->deactivate_full + s->deactivate_empty + | ||
469 | s->deactivate_to_head + s->deactivate_to_tail + s->deactivate_bypass; | ||
470 | |||
471 | if (total) { | ||
472 | printf("\nSlab Deactivation Ocurrences %%\n"); | ||
473 | printf("-------------------------------------------------\n"); | ||
474 | printf("Slab full %7lu %3lu%%\n", | ||
475 | s->deactivate_full, (s->deactivate_full * 100) / total); | ||
476 | printf("Slab empty %7lu %3lu%%\n", | ||
477 | s->deactivate_empty, (s->deactivate_empty * 100) / total); | ||
478 | printf("Moved to head of partial list %7lu %3lu%%\n", | ||
479 | s->deactivate_to_head, (s->deactivate_to_head * 100) / total); | ||
480 | printf("Moved to tail of partial list %7lu %3lu%%\n", | ||
481 | s->deactivate_to_tail, (s->deactivate_to_tail * 100) / total); | ||
482 | printf("Deactivation bypass %7lu %3lu%%\n", | ||
483 | s->deactivate_bypass, (s->deactivate_bypass * 100) / total); | ||
484 | printf("Refilled from foreign frees %7lu %3lu%%\n", | ||
485 | s->alloc_refill, (s->alloc_refill * 100) / total); | ||
486 | printf("Node mismatch %7lu %3lu%%\n", | ||
487 | s->alloc_node_mismatch, (s->alloc_node_mismatch * 100) / total); | ||
488 | } | ||
489 | |||
490 | if (s->cmpxchg_double_fail || s->cmpxchg_double_cpu_fail) | ||
491 | printf("\nCmpxchg_double Looping\n------------------------\n"); | ||
492 | printf("Locked Cmpxchg Double redos %lu\nUnlocked Cmpxchg Double redos %lu\n", | ||
493 | s->cmpxchg_double_fail, s->cmpxchg_double_cpu_fail); | ||
494 | } | ||
495 | |||
496 | static void report(struct slabinfo *s) | ||
497 | { | ||
498 | if (strcmp(s->name, "*") == 0) | ||
499 | return; | ||
500 | |||
501 | printf("\nSlabcache: %-20s Aliases: %2d Order : %2d Objects: %lu\n", | ||
502 | s->name, s->aliases, s->order, s->objects); | ||
503 | if (s->hwcache_align) | ||
504 | printf("** Hardware cacheline aligned\n"); | ||
505 | if (s->cache_dma) | ||
506 | printf("** Memory is allocated in a special DMA zone\n"); | ||
507 | if (s->destroy_by_rcu) | ||
508 | printf("** Slabs are destroyed via RCU\n"); | ||
509 | if (s->reclaim_account) | ||
510 | printf("** Reclaim accounting active\n"); | ||
511 | |||
512 | printf("\nSizes (bytes) Slabs Debug Memory\n"); | ||
513 | printf("------------------------------------------------------------------------\n"); | ||
514 | printf("Object : %7d Total : %7ld Sanity Checks : %s Total: %7ld\n", | ||
515 | s->object_size, s->slabs, onoff(s->sanity_checks), | ||
516 | s->slabs * (page_size << s->order)); | ||
517 | printf("SlabObj: %7d Full : %7ld Redzoning : %s Used : %7ld\n", | ||
518 | s->slab_size, s->slabs - s->partial - s->cpu_slabs, | ||
519 | onoff(s->red_zone), s->objects * s->object_size); | ||
520 | printf("SlabSiz: %7d Partial: %7ld Poisoning : %s Loss : %7ld\n", | ||
521 | page_size << s->order, s->partial, onoff(s->poison), | ||
522 | s->slabs * (page_size << s->order) - s->objects * s->object_size); | ||
523 | printf("Loss : %7d CpuSlab: %7d Tracking : %s Lalig: %7ld\n", | ||
524 | s->slab_size - s->object_size, s->cpu_slabs, onoff(s->store_user), | ||
525 | (s->slab_size - s->object_size) * s->objects); | ||
526 | printf("Align : %7d Objects: %7d Tracing : %s Lpadd: %7ld\n", | ||
527 | s->align, s->objs_per_slab, onoff(s->trace), | ||
528 | ((page_size << s->order) - s->objs_per_slab * s->slab_size) * | ||
529 | s->slabs); | ||
530 | |||
531 | ops(s); | ||
532 | show_tracking(s); | ||
533 | slab_numa(s, 1); | ||
534 | slab_stats(s); | ||
535 | } | ||
536 | |||
537 | static void slabcache(struct slabinfo *s) | ||
538 | { | ||
539 | char size_str[20]; | ||
540 | char dist_str[40]; | ||
541 | char flags[20]; | ||
542 | char *p = flags; | ||
543 | |||
544 | if (strcmp(s->name, "*") == 0) | ||
545 | return; | ||
546 | |||
547 | if (actual_slabs == 1) { | ||
548 | report(s); | ||
549 | return; | ||
550 | } | ||
551 | |||
552 | if (skip_zero && !show_empty && !s->slabs) | ||
553 | return; | ||
554 | |||
555 | if (show_empty && s->slabs) | ||
556 | return; | ||
557 | |||
558 | store_size(size_str, slab_size(s)); | ||
559 | snprintf(dist_str, 40, "%lu/%lu/%d", s->slabs - s->cpu_slabs, | ||
560 | s->partial, s->cpu_slabs); | ||
561 | |||
562 | if (!line++) | ||
563 | first_line(); | ||
564 | |||
565 | if (s->aliases) | ||
566 | *p++ = '*'; | ||
567 | if (s->cache_dma) | ||
568 | *p++ = 'd'; | ||
569 | if (s->hwcache_align) | ||
570 | *p++ = 'A'; | ||
571 | if (s->poison) | ||
572 | *p++ = 'P'; | ||
573 | if (s->reclaim_account) | ||
574 | *p++ = 'a'; | ||
575 | if (s->red_zone) | ||
576 | *p++ = 'Z'; | ||
577 | if (s->sanity_checks) | ||
578 | *p++ = 'F'; | ||
579 | if (s->store_user) | ||
580 | *p++ = 'U'; | ||
581 | if (s->trace) | ||
582 | *p++ = 'T'; | ||
583 | |||
584 | *p = 0; | ||
585 | if (show_activity) { | ||
586 | unsigned long total_alloc; | ||
587 | unsigned long total_free; | ||
588 | |||
589 | total_alloc = s->alloc_fastpath + s->alloc_slowpath; | ||
590 | total_free = s->free_fastpath + s->free_slowpath; | ||
591 | |||
592 | printf("%-21s %8ld %10ld %10ld %3ld %3ld %5ld %1d %4ld %4ld\n", | ||
593 | s->name, s->objects, | ||
594 | total_alloc, total_free, | ||
595 | total_alloc ? (s->alloc_fastpath * 100 / total_alloc) : 0, | ||
596 | total_free ? (s->free_fastpath * 100 / total_free) : 0, | ||
597 | s->order_fallback, s->order, s->cmpxchg_double_fail, | ||
598 | s->cmpxchg_double_cpu_fail); | ||
599 | } | ||
600 | else | ||
601 | printf("%-21s %8ld %7d %8s %14s %4d %1d %3ld %3ld %s\n", | ||
602 | s->name, s->objects, s->object_size, size_str, dist_str, | ||
603 | s->objs_per_slab, s->order, | ||
604 | s->slabs ? (s->partial * 100) / s->slabs : 100, | ||
605 | s->slabs ? (s->objects * s->object_size * 100) / | ||
606 | (s->slabs * (page_size << s->order)) : 100, | ||
607 | flags); | ||
608 | } | ||
609 | |||
610 | /* | ||
611 | * Analyze debug options. Return false if something is amiss. | ||
612 | */ | ||
613 | static int debug_opt_scan(char *opt) | ||
614 | { | ||
615 | if (!opt || !opt[0] || strcmp(opt, "-") == 0) | ||
616 | return 1; | ||
617 | |||
618 | if (strcasecmp(opt, "a") == 0) { | ||
619 | sanity = 1; | ||
620 | poison = 1; | ||
621 | redzone = 1; | ||
622 | tracking = 1; | ||
623 | return 1; | ||
624 | } | ||
625 | |||
626 | for ( ; *opt; opt++) | ||
627 | switch (*opt) { | ||
628 | case 'F' : case 'f': | ||
629 | if (sanity) | ||
630 | return 0; | ||
631 | sanity = 1; | ||
632 | break; | ||
633 | case 'P' : case 'p': | ||
634 | if (poison) | ||
635 | return 0; | ||
636 | poison = 1; | ||
637 | break; | ||
638 | |||
639 | case 'Z' : case 'z': | ||
640 | if (redzone) | ||
641 | return 0; | ||
642 | redzone = 1; | ||
643 | break; | ||
644 | |||
645 | case 'U' : case 'u': | ||
646 | if (tracking) | ||
647 | return 0; | ||
648 | tracking = 1; | ||
649 | break; | ||
650 | |||
651 | case 'T' : case 't': | ||
652 | if (tracing) | ||
653 | return 0; | ||
654 | tracing = 1; | ||
655 | break; | ||
656 | default: | ||
657 | return 0; | ||
658 | } | ||
659 | return 1; | ||
660 | } | ||
661 | |||
662 | static int slab_empty(struct slabinfo *s) | ||
663 | { | ||
664 | if (s->objects > 0) | ||
665 | return 0; | ||
666 | |||
667 | /* | ||
668 | * We may still have slabs even if there are no objects. Shrinking will | ||
669 | * remove them. | ||
670 | */ | ||
671 | if (s->slabs != 0) | ||
672 | set_obj(s, "shrink", 1); | ||
673 | |||
674 | return 1; | ||
675 | } | ||
676 | |||
677 | static void slab_debug(struct slabinfo *s) | ||
678 | { | ||
679 | if (strcmp(s->name, "*") == 0) | ||
680 | return; | ||
681 | |||
682 | if (sanity && !s->sanity_checks) { | ||
683 | set_obj(s, "sanity", 1); | ||
684 | } | ||
685 | if (!sanity && s->sanity_checks) { | ||
686 | if (slab_empty(s)) | ||
687 | set_obj(s, "sanity", 0); | ||
688 | else | ||
689 | fprintf(stderr, "%s not empty cannot disable sanity checks\n", s->name); | ||
690 | } | ||
691 | if (redzone && !s->red_zone) { | ||
692 | if (slab_empty(s)) | ||
693 | set_obj(s, "red_zone", 1); | ||
694 | else | ||
695 | fprintf(stderr, "%s not empty cannot enable redzoning\n", s->name); | ||
696 | } | ||
697 | if (!redzone && s->red_zone) { | ||
698 | if (slab_empty(s)) | ||
699 | set_obj(s, "red_zone", 0); | ||
700 | else | ||
701 | fprintf(stderr, "%s not empty cannot disable redzoning\n", s->name); | ||
702 | } | ||
703 | if (poison && !s->poison) { | ||
704 | if (slab_empty(s)) | ||
705 | set_obj(s, "poison", 1); | ||
706 | else | ||
707 | fprintf(stderr, "%s not empty cannot enable poisoning\n", s->name); | ||
708 | } | ||
709 | if (!poison && s->poison) { | ||
710 | if (slab_empty(s)) | ||
711 | set_obj(s, "poison", 0); | ||
712 | else | ||
713 | fprintf(stderr, "%s not empty cannot disable poisoning\n", s->name); | ||
714 | } | ||
715 | if (tracking && !s->store_user) { | ||
716 | if (slab_empty(s)) | ||
717 | set_obj(s, "store_user", 1); | ||
718 | else | ||
719 | fprintf(stderr, "%s not empty cannot enable tracking\n", s->name); | ||
720 | } | ||
721 | if (!tracking && s->store_user) { | ||
722 | if (slab_empty(s)) | ||
723 | set_obj(s, "store_user", 0); | ||
724 | else | ||
725 | fprintf(stderr, "%s not empty cannot disable tracking\n", s->name); | ||
726 | } | ||
727 | if (tracing && !s->trace) { | ||
728 | if (slabs == 1) | ||
729 | set_obj(s, "trace", 1); | ||
730 | else | ||
731 | fprintf(stderr, "%s can only enable trace for one slab at a time\n", s->name); | ||
732 | } | ||
733 | if (!tracing && s->trace) | ||
734 | set_obj(s, "trace", 1); | ||
735 | } | ||
736 | |||
737 | static void totals(void) | ||
738 | { | ||
739 | struct slabinfo *s; | ||
740 | |||
741 | int used_slabs = 0; | ||
742 | char b1[20], b2[20], b3[20], b4[20]; | ||
743 | unsigned long long max = 1ULL << 63; | ||
744 | |||
745 | /* Object size */ | ||
746 | unsigned long long min_objsize = max, max_objsize = 0, avg_objsize; | ||
747 | |||
748 | /* Number of partial slabs in a slabcache */ | ||
749 | unsigned long long min_partial = max, max_partial = 0, | ||
750 | avg_partial, total_partial = 0; | ||
751 | |||
752 | /* Number of slabs in a slab cache */ | ||
753 | unsigned long long min_slabs = max, max_slabs = 0, | ||
754 | avg_slabs, total_slabs = 0; | ||
755 | |||
756 | /* Size of the whole slab */ | ||
757 | unsigned long long min_size = max, max_size = 0, | ||
758 | avg_size, total_size = 0; | ||
759 | |||
760 | /* Bytes used for object storage in a slab */ | ||
761 | unsigned long long min_used = max, max_used = 0, | ||
762 | avg_used, total_used = 0; | ||
763 | |||
764 | /* Waste: Bytes used for alignment and padding */ | ||
765 | unsigned long long min_waste = max, max_waste = 0, | ||
766 | avg_waste, total_waste = 0; | ||
767 | /* Number of objects in a slab */ | ||
768 | unsigned long long min_objects = max, max_objects = 0, | ||
769 | avg_objects, total_objects = 0; | ||
770 | /* Waste per object */ | ||
771 | unsigned long long min_objwaste = max, | ||
772 | max_objwaste = 0, avg_objwaste, | ||
773 | total_objwaste = 0; | ||
774 | |||
775 | /* Memory per object */ | ||
776 | unsigned long long min_memobj = max, | ||
777 | max_memobj = 0, avg_memobj, | ||
778 | total_objsize = 0; | ||
779 | |||
780 | /* Percentage of partial slabs per slab */ | ||
781 | unsigned long min_ppart = 100, max_ppart = 0, | ||
782 | avg_ppart, total_ppart = 0; | ||
783 | |||
784 | /* Number of objects in partial slabs */ | ||
785 | unsigned long min_partobj = max, max_partobj = 0, | ||
786 | avg_partobj, total_partobj = 0; | ||
787 | |||
788 | /* Percentage of partial objects of all objects in a slab */ | ||
789 | unsigned long min_ppartobj = 100, max_ppartobj = 0, | ||
790 | avg_ppartobj, total_ppartobj = 0; | ||
791 | |||
792 | |||
793 | for (s = slabinfo; s < slabinfo + slabs; s++) { | ||
794 | unsigned long long size; | ||
795 | unsigned long used; | ||
796 | unsigned long long wasted; | ||
797 | unsigned long long objwaste; | ||
798 | unsigned long percentage_partial_slabs; | ||
799 | unsigned long percentage_partial_objs; | ||
800 | |||
801 | if (!s->slabs || !s->objects) | ||
802 | continue; | ||
803 | |||
804 | used_slabs++; | ||
805 | |||
806 | size = slab_size(s); | ||
807 | used = s->objects * s->object_size; | ||
808 | wasted = size - used; | ||
809 | objwaste = s->slab_size - s->object_size; | ||
810 | |||
811 | percentage_partial_slabs = s->partial * 100 / s->slabs; | ||
812 | if (percentage_partial_slabs > 100) | ||
813 | percentage_partial_slabs = 100; | ||
814 | |||
815 | percentage_partial_objs = s->objects_partial * 100 | ||
816 | / s->objects; | ||
817 | |||
818 | if (percentage_partial_objs > 100) | ||
819 | percentage_partial_objs = 100; | ||
820 | |||
821 | if (s->object_size < min_objsize) | ||
822 | min_objsize = s->object_size; | ||
823 | if (s->partial < min_partial) | ||
824 | min_partial = s->partial; | ||
825 | if (s->slabs < min_slabs) | ||
826 | min_slabs = s->slabs; | ||
827 | if (size < min_size) | ||
828 | min_size = size; | ||
829 | if (wasted < min_waste) | ||
830 | min_waste = wasted; | ||
831 | if (objwaste < min_objwaste) | ||
832 | min_objwaste = objwaste; | ||
833 | if (s->objects < min_objects) | ||
834 | min_objects = s->objects; | ||
835 | if (used < min_used) | ||
836 | min_used = used; | ||
837 | if (s->objects_partial < min_partobj) | ||
838 | min_partobj = s->objects_partial; | ||
839 | if (percentage_partial_slabs < min_ppart) | ||
840 | min_ppart = percentage_partial_slabs; | ||
841 | if (percentage_partial_objs < min_ppartobj) | ||
842 | min_ppartobj = percentage_partial_objs; | ||
843 | if (s->slab_size < min_memobj) | ||
844 | min_memobj = s->slab_size; | ||
845 | |||
846 | if (s->object_size > max_objsize) | ||
847 | max_objsize = s->object_size; | ||
848 | if (s->partial > max_partial) | ||
849 | max_partial = s->partial; | ||
850 | if (s->slabs > max_slabs) | ||
851 | max_slabs = s->slabs; | ||
852 | if (size > max_size) | ||
853 | max_size = size; | ||
854 | if (wasted > max_waste) | ||
855 | max_waste = wasted; | ||
856 | if (objwaste > max_objwaste) | ||
857 | max_objwaste = objwaste; | ||
858 | if (s->objects > max_objects) | ||
859 | max_objects = s->objects; | ||
860 | if (used > max_used) | ||
861 | max_used = used; | ||
862 | if (s->objects_partial > max_partobj) | ||
863 | max_partobj = s->objects_partial; | ||
864 | if (percentage_partial_slabs > max_ppart) | ||
865 | max_ppart = percentage_partial_slabs; | ||
866 | if (percentage_partial_objs > max_ppartobj) | ||
867 | max_ppartobj = percentage_partial_objs; | ||
868 | if (s->slab_size > max_memobj) | ||
869 | max_memobj = s->slab_size; | ||
870 | |||
871 | total_partial += s->partial; | ||
872 | total_slabs += s->slabs; | ||
873 | total_size += size; | ||
874 | total_waste += wasted; | ||
875 | |||
876 | total_objects += s->objects; | ||
877 | total_used += used; | ||
878 | total_partobj += s->objects_partial; | ||
879 | total_ppart += percentage_partial_slabs; | ||
880 | total_ppartobj += percentage_partial_objs; | ||
881 | |||
882 | total_objwaste += s->objects * objwaste; | ||
883 | total_objsize += s->objects * s->slab_size; | ||
884 | } | ||
885 | |||
886 | if (!total_objects) { | ||
887 | printf("No objects\n"); | ||
888 | return; | ||
889 | } | ||
890 | if (!used_slabs) { | ||
891 | printf("No slabs\n"); | ||
892 | return; | ||
893 | } | ||
894 | |||
895 | /* Per slab averages */ | ||
896 | avg_partial = total_partial / used_slabs; | ||
897 | avg_slabs = total_slabs / used_slabs; | ||
898 | avg_size = total_size / used_slabs; | ||
899 | avg_waste = total_waste / used_slabs; | ||
900 | |||
901 | avg_objects = total_objects / used_slabs; | ||
902 | avg_used = total_used / used_slabs; | ||
903 | avg_partobj = total_partobj / used_slabs; | ||
904 | avg_ppart = total_ppart / used_slabs; | ||
905 | avg_ppartobj = total_ppartobj / used_slabs; | ||
906 | |||
907 | /* Per object object sizes */ | ||
908 | avg_objsize = total_used / total_objects; | ||
909 | avg_objwaste = total_objwaste / total_objects; | ||
910 | avg_partobj = total_partobj * 100 / total_objects; | ||
911 | avg_memobj = total_objsize / total_objects; | ||
912 | |||
913 | printf("Slabcache Totals\n"); | ||
914 | printf("----------------\n"); | ||
915 | printf("Slabcaches : %3d Aliases : %3d->%-3d Active: %3d\n", | ||
916 | slabs, aliases, alias_targets, used_slabs); | ||
917 | |||
918 | store_size(b1, total_size);store_size(b2, total_waste); | ||
919 | store_size(b3, total_waste * 100 / total_used); | ||
920 | printf("Memory used: %6s # Loss : %6s MRatio:%6s%%\n", b1, b2, b3); | ||
921 | |||
922 | store_size(b1, total_objects);store_size(b2, total_partobj); | ||
923 | store_size(b3, total_partobj * 100 / total_objects); | ||
924 | printf("# Objects : %6s # PartObj: %6s ORatio:%6s%%\n", b1, b2, b3); | ||
925 | |||
926 | printf("\n"); | ||
927 | printf("Per Cache Average Min Max Total\n"); | ||
928 | printf("---------------------------------------------------------\n"); | ||
929 | |||
930 | store_size(b1, avg_objects);store_size(b2, min_objects); | ||
931 | store_size(b3, max_objects);store_size(b4, total_objects); | ||
932 | printf("#Objects %10s %10s %10s %10s\n", | ||
933 | b1, b2, b3, b4); | ||
934 | |||
935 | store_size(b1, avg_slabs);store_size(b2, min_slabs); | ||
936 | store_size(b3, max_slabs);store_size(b4, total_slabs); | ||
937 | printf("#Slabs %10s %10s %10s %10s\n", | ||
938 | b1, b2, b3, b4); | ||
939 | |||
940 | store_size(b1, avg_partial);store_size(b2, min_partial); | ||
941 | store_size(b3, max_partial);store_size(b4, total_partial); | ||
942 | printf("#PartSlab %10s %10s %10s %10s\n", | ||
943 | b1, b2, b3, b4); | ||
944 | store_size(b1, avg_ppart);store_size(b2, min_ppart); | ||
945 | store_size(b3, max_ppart); | ||
946 | store_size(b4, total_partial * 100 / total_slabs); | ||
947 | printf("%%PartSlab%10s%% %10s%% %10s%% %10s%%\n", | ||
948 | b1, b2, b3, b4); | ||
949 | |||
950 | store_size(b1, avg_partobj);store_size(b2, min_partobj); | ||
951 | store_size(b3, max_partobj); | ||
952 | store_size(b4, total_partobj); | ||
953 | printf("PartObjs %10s %10s %10s %10s\n", | ||
954 | b1, b2, b3, b4); | ||
955 | |||
956 | store_size(b1, avg_ppartobj);store_size(b2, min_ppartobj); | ||
957 | store_size(b3, max_ppartobj); | ||
958 | store_size(b4, total_partobj * 100 / total_objects); | ||
959 | printf("%% PartObj%10s%% %10s%% %10s%% %10s%%\n", | ||
960 | b1, b2, b3, b4); | ||
961 | |||
962 | store_size(b1, avg_size);store_size(b2, min_size); | ||
963 | store_size(b3, max_size);store_size(b4, total_size); | ||
964 | printf("Memory %10s %10s %10s %10s\n", | ||
965 | b1, b2, b3, b4); | ||
966 | |||
967 | store_size(b1, avg_used);store_size(b2, min_used); | ||
968 | store_size(b3, max_used);store_size(b4, total_used); | ||
969 | printf("Used %10s %10s %10s %10s\n", | ||
970 | b1, b2, b3, b4); | ||
971 | |||
972 | store_size(b1, avg_waste);store_size(b2, min_waste); | ||
973 | store_size(b3, max_waste);store_size(b4, total_waste); | ||
974 | printf("Loss %10s %10s %10s %10s\n", | ||
975 | b1, b2, b3, b4); | ||
976 | |||
977 | printf("\n"); | ||
978 | printf("Per Object Average Min Max\n"); | ||
979 | printf("---------------------------------------------\n"); | ||
980 | |||
981 | store_size(b1, avg_memobj);store_size(b2, min_memobj); | ||
982 | store_size(b3, max_memobj); | ||
983 | printf("Memory %10s %10s %10s\n", | ||
984 | b1, b2, b3); | ||
985 | store_size(b1, avg_objsize);store_size(b2, min_objsize); | ||
986 | store_size(b3, max_objsize); | ||
987 | printf("User %10s %10s %10s\n", | ||
988 | b1, b2, b3); | ||
989 | |||
990 | store_size(b1, avg_objwaste);store_size(b2, min_objwaste); | ||
991 | store_size(b3, max_objwaste); | ||
992 | printf("Loss %10s %10s %10s\n", | ||
993 | b1, b2, b3); | ||
994 | } | ||
995 | |||
996 | static void sort_slabs(void) | ||
997 | { | ||
998 | struct slabinfo *s1,*s2; | ||
999 | |||
1000 | for (s1 = slabinfo; s1 < slabinfo + slabs; s1++) { | ||
1001 | for (s2 = s1 + 1; s2 < slabinfo + slabs; s2++) { | ||
1002 | int result; | ||
1003 | |||
1004 | if (sort_size) | ||
1005 | result = slab_size(s1) < slab_size(s2); | ||
1006 | else if (sort_active) | ||
1007 | result = slab_activity(s1) < slab_activity(s2); | ||
1008 | else | ||
1009 | result = strcasecmp(s1->name, s2->name); | ||
1010 | |||
1011 | if (show_inverted) | ||
1012 | result = -result; | ||
1013 | |||
1014 | if (result > 0) { | ||
1015 | struct slabinfo t; | ||
1016 | |||
1017 | memcpy(&t, s1, sizeof(struct slabinfo)); | ||
1018 | memcpy(s1, s2, sizeof(struct slabinfo)); | ||
1019 | memcpy(s2, &t, sizeof(struct slabinfo)); | ||
1020 | } | ||
1021 | } | ||
1022 | } | ||
1023 | } | ||
1024 | |||
1025 | static void sort_aliases(void) | ||
1026 | { | ||
1027 | struct aliasinfo *a1,*a2; | ||
1028 | |||
1029 | for (a1 = aliasinfo; a1 < aliasinfo + aliases; a1++) { | ||
1030 | for (a2 = a1 + 1; a2 < aliasinfo + aliases; a2++) { | ||
1031 | char *n1, *n2; | ||
1032 | |||
1033 | n1 = a1->name; | ||
1034 | n2 = a2->name; | ||
1035 | if (show_alias && !show_inverted) { | ||
1036 | n1 = a1->ref; | ||
1037 | n2 = a2->ref; | ||
1038 | } | ||
1039 | if (strcasecmp(n1, n2) > 0) { | ||
1040 | struct aliasinfo t; | ||
1041 | |||
1042 | memcpy(&t, a1, sizeof(struct aliasinfo)); | ||
1043 | memcpy(a1, a2, sizeof(struct aliasinfo)); | ||
1044 | memcpy(a2, &t, sizeof(struct aliasinfo)); | ||
1045 | } | ||
1046 | } | ||
1047 | } | ||
1048 | } | ||
1049 | |||
1050 | static void link_slabs(void) | ||
1051 | { | ||
1052 | struct aliasinfo *a; | ||
1053 | struct slabinfo *s; | ||
1054 | |||
1055 | for (a = aliasinfo; a < aliasinfo + aliases; a++) { | ||
1056 | |||
1057 | for (s = slabinfo; s < slabinfo + slabs; s++) | ||
1058 | if (strcmp(a->ref, s->name) == 0) { | ||
1059 | a->slab = s; | ||
1060 | s->refs++; | ||
1061 | break; | ||
1062 | } | ||
1063 | if (s == slabinfo + slabs) | ||
1064 | fatal("Unresolved alias %s\n", a->ref); | ||
1065 | } | ||
1066 | } | ||
1067 | |||
1068 | static void alias(void) | ||
1069 | { | ||
1070 | struct aliasinfo *a; | ||
1071 | char *active = NULL; | ||
1072 | |||
1073 | sort_aliases(); | ||
1074 | link_slabs(); | ||
1075 | |||
1076 | for(a = aliasinfo; a < aliasinfo + aliases; a++) { | ||
1077 | |||
1078 | if (!show_single_ref && a->slab->refs == 1) | ||
1079 | continue; | ||
1080 | |||
1081 | if (!show_inverted) { | ||
1082 | if (active) { | ||
1083 | if (strcmp(a->slab->name, active) == 0) { | ||
1084 | printf(" %s", a->name); | ||
1085 | continue; | ||
1086 | } | ||
1087 | } | ||
1088 | printf("\n%-12s <- %s", a->slab->name, a->name); | ||
1089 | active = a->slab->name; | ||
1090 | } | ||
1091 | else | ||
1092 | printf("%-20s -> %s\n", a->name, a->slab->name); | ||
1093 | } | ||
1094 | if (active) | ||
1095 | printf("\n"); | ||
1096 | } | ||
1097 | |||
1098 | |||
1099 | static void rename_slabs(void) | ||
1100 | { | ||
1101 | struct slabinfo *s; | ||
1102 | struct aliasinfo *a; | ||
1103 | |||
1104 | for (s = slabinfo; s < slabinfo + slabs; s++) { | ||
1105 | if (*s->name != ':') | ||
1106 | continue; | ||
1107 | |||
1108 | if (s->refs > 1 && !show_first_alias) | ||
1109 | continue; | ||
1110 | |||
1111 | a = find_one_alias(s); | ||
1112 | |||
1113 | if (a) | ||
1114 | s->name = a->name; | ||
1115 | else { | ||
1116 | s->name = "*"; | ||
1117 | actual_slabs--; | ||
1118 | } | ||
1119 | } | ||
1120 | } | ||
1121 | |||
1122 | static int slab_mismatch(char *slab) | ||
1123 | { | ||
1124 | return regexec(&pattern, slab, 0, NULL, 0); | ||
1125 | } | ||
1126 | |||
1127 | static void read_slab_dir(void) | ||
1128 | { | ||
1129 | DIR *dir; | ||
1130 | struct dirent *de; | ||
1131 | struct slabinfo *slab = slabinfo; | ||
1132 | struct aliasinfo *alias = aliasinfo; | ||
1133 | char *p; | ||
1134 | char *t; | ||
1135 | int count; | ||
1136 | |||
1137 | if (chdir("/sys/kernel/slab") && chdir("/sys/slab")) | ||
1138 | fatal("SYSFS support for SLUB not active\n"); | ||
1139 | |||
1140 | dir = opendir("."); | ||
1141 | while ((de = readdir(dir))) { | ||
1142 | if (de->d_name[0] == '.' || | ||
1143 | (de->d_name[0] != ':' && slab_mismatch(de->d_name))) | ||
1144 | continue; | ||
1145 | switch (de->d_type) { | ||
1146 | case DT_LNK: | ||
1147 | alias->name = strdup(de->d_name); | ||
1148 | count = readlink(de->d_name, buffer, sizeof(buffer)); | ||
1149 | |||
1150 | if (count < 0) | ||
1151 | fatal("Cannot read symlink %s\n", de->d_name); | ||
1152 | |||
1153 | buffer[count] = 0; | ||
1154 | p = buffer + count; | ||
1155 | while (p > buffer && p[-1] != '/') | ||
1156 | p--; | ||
1157 | alias->ref = strdup(p); | ||
1158 | alias++; | ||
1159 | break; | ||
1160 | case DT_DIR: | ||
1161 | if (chdir(de->d_name)) | ||
1162 | fatal("Unable to access slab %s\n", slab->name); | ||
1163 | slab->name = strdup(de->d_name); | ||
1164 | slab->alias = 0; | ||
1165 | slab->refs = 0; | ||
1166 | slab->aliases = get_obj("aliases"); | ||
1167 | slab->align = get_obj("align"); | ||
1168 | slab->cache_dma = get_obj("cache_dma"); | ||
1169 | slab->cpu_slabs = get_obj("cpu_slabs"); | ||
1170 | slab->destroy_by_rcu = get_obj("destroy_by_rcu"); | ||
1171 | slab->hwcache_align = get_obj("hwcache_align"); | ||
1172 | slab->object_size = get_obj("object_size"); | ||
1173 | slab->objects = get_obj("objects"); | ||
1174 | slab->objects_partial = get_obj("objects_partial"); | ||
1175 | slab->objects_total = get_obj("objects_total"); | ||
1176 | slab->objs_per_slab = get_obj("objs_per_slab"); | ||
1177 | slab->order = get_obj("order"); | ||
1178 | slab->partial = get_obj("partial"); | ||
1179 | slab->partial = get_obj_and_str("partial", &t); | ||
1180 | decode_numa_list(slab->numa_partial, t); | ||
1181 | free(t); | ||
1182 | slab->poison = get_obj("poison"); | ||
1183 | slab->reclaim_account = get_obj("reclaim_account"); | ||
1184 | slab->red_zone = get_obj("red_zone"); | ||
1185 | slab->sanity_checks = get_obj("sanity_checks"); | ||
1186 | slab->slab_size = get_obj("slab_size"); | ||
1187 | slab->slabs = get_obj_and_str("slabs", &t); | ||
1188 | decode_numa_list(slab->numa, t); | ||
1189 | free(t); | ||
1190 | slab->store_user = get_obj("store_user"); | ||
1191 | slab->trace = get_obj("trace"); | ||
1192 | slab->alloc_fastpath = get_obj("alloc_fastpath"); | ||
1193 | slab->alloc_slowpath = get_obj("alloc_slowpath"); | ||
1194 | slab->free_fastpath = get_obj("free_fastpath"); | ||
1195 | slab->free_slowpath = get_obj("free_slowpath"); | ||
1196 | slab->free_frozen= get_obj("free_frozen"); | ||
1197 | slab->free_add_partial = get_obj("free_add_partial"); | ||
1198 | slab->free_remove_partial = get_obj("free_remove_partial"); | ||
1199 | slab->alloc_from_partial = get_obj("alloc_from_partial"); | ||
1200 | slab->alloc_slab = get_obj("alloc_slab"); | ||
1201 | slab->alloc_refill = get_obj("alloc_refill"); | ||
1202 | slab->free_slab = get_obj("free_slab"); | ||
1203 | slab->cpuslab_flush = get_obj("cpuslab_flush"); | ||
1204 | slab->deactivate_full = get_obj("deactivate_full"); | ||
1205 | slab->deactivate_empty = get_obj("deactivate_empty"); | ||
1206 | slab->deactivate_to_head = get_obj("deactivate_to_head"); | ||
1207 | slab->deactivate_to_tail = get_obj("deactivate_to_tail"); | ||
1208 | slab->deactivate_remote_frees = get_obj("deactivate_remote_frees"); | ||
1209 | slab->order_fallback = get_obj("order_fallback"); | ||
1210 | slab->cmpxchg_double_cpu_fail = get_obj("cmpxchg_double_cpu_fail"); | ||
1211 | slab->cmpxchg_double_fail = get_obj("cmpxchg_double_fail"); | ||
1212 | slab->alloc_node_mismatch = get_obj("alloc_node_mismatch"); | ||
1213 | slab->deactivate_bypass = get_obj("deactivate_bypass"); | ||
1214 | chdir(".."); | ||
1215 | if (slab->name[0] == ':') | ||
1216 | alias_targets++; | ||
1217 | slab++; | ||
1218 | break; | ||
1219 | default : | ||
1220 | fatal("Unknown file type %lx\n", de->d_type); | ||
1221 | } | ||
1222 | } | ||
1223 | closedir(dir); | ||
1224 | slabs = slab - slabinfo; | ||
1225 | actual_slabs = slabs; | ||
1226 | aliases = alias - aliasinfo; | ||
1227 | if (slabs > MAX_SLABS) | ||
1228 | fatal("Too many slabs\n"); | ||
1229 | if (aliases > MAX_ALIASES) | ||
1230 | fatal("Too many aliases\n"); | ||
1231 | } | ||
1232 | |||
1233 | static void output_slabs(void) | ||
1234 | { | ||
1235 | struct slabinfo *slab; | ||
1236 | |||
1237 | for (slab = slabinfo; slab < slabinfo + slabs; slab++) { | ||
1238 | |||
1239 | if (slab->alias) | ||
1240 | continue; | ||
1241 | |||
1242 | |||
1243 | if (show_numa) | ||
1244 | slab_numa(slab, 0); | ||
1245 | else if (show_track) | ||
1246 | show_tracking(slab); | ||
1247 | else if (validate) | ||
1248 | slab_validate(slab); | ||
1249 | else if (shrink) | ||
1250 | slab_shrink(slab); | ||
1251 | else if (set_debug) | ||
1252 | slab_debug(slab); | ||
1253 | else if (show_ops) | ||
1254 | ops(slab); | ||
1255 | else if (show_slab) | ||
1256 | slabcache(slab); | ||
1257 | else if (show_report) | ||
1258 | report(slab); | ||
1259 | } | ||
1260 | } | ||
1261 | |||
1262 | struct option opts[] = { | ||
1263 | { "aliases", 0, NULL, 'a' }, | ||
1264 | { "activity", 0, NULL, 'A' }, | ||
1265 | { "debug", 2, NULL, 'd' }, | ||
1266 | { "display-activity", 0, NULL, 'D' }, | ||
1267 | { "empty", 0, NULL, 'e' }, | ||
1268 | { "first-alias", 0, NULL, 'f' }, | ||
1269 | { "help", 0, NULL, 'h' }, | ||
1270 | { "inverted", 0, NULL, 'i'}, | ||
1271 | { "numa", 0, NULL, 'n' }, | ||
1272 | { "ops", 0, NULL, 'o' }, | ||
1273 | { "report", 0, NULL, 'r' }, | ||
1274 | { "shrink", 0, NULL, 's' }, | ||
1275 | { "slabs", 0, NULL, 'l' }, | ||
1276 | { "track", 0, NULL, 't'}, | ||
1277 | { "validate", 0, NULL, 'v' }, | ||
1278 | { "zero", 0, NULL, 'z' }, | ||
1279 | { "1ref", 0, NULL, '1'}, | ||
1280 | { NULL, 0, NULL, 0 } | ||
1281 | }; | ||
1282 | |||
1283 | int main(int argc, char *argv[]) | ||
1284 | { | ||
1285 | int c; | ||
1286 | int err; | ||
1287 | char *pattern_source; | ||
1288 | |||
1289 | page_size = getpagesize(); | ||
1290 | |||
1291 | while ((c = getopt_long(argc, argv, "aAd::Defhil1noprstvzTS", | ||
1292 | opts, NULL)) != -1) | ||
1293 | switch (c) { | ||
1294 | case '1': | ||
1295 | show_single_ref = 1; | ||
1296 | break; | ||
1297 | case 'a': | ||
1298 | show_alias = 1; | ||
1299 | break; | ||
1300 | case 'A': | ||
1301 | sort_active = 1; | ||
1302 | break; | ||
1303 | case 'd': | ||
1304 | set_debug = 1; | ||
1305 | if (!debug_opt_scan(optarg)) | ||
1306 | fatal("Invalid debug option '%s'\n", optarg); | ||
1307 | break; | ||
1308 | case 'D': | ||
1309 | show_activity = 1; | ||
1310 | break; | ||
1311 | case 'e': | ||
1312 | show_empty = 1; | ||
1313 | break; | ||
1314 | case 'f': | ||
1315 | show_first_alias = 1; | ||
1316 | break; | ||
1317 | case 'h': | ||
1318 | usage(); | ||
1319 | return 0; | ||
1320 | case 'i': | ||
1321 | show_inverted = 1; | ||
1322 | break; | ||
1323 | case 'n': | ||
1324 | show_numa = 1; | ||
1325 | break; | ||
1326 | case 'o': | ||
1327 | show_ops = 1; | ||
1328 | break; | ||
1329 | case 'r': | ||
1330 | show_report = 1; | ||
1331 | break; | ||
1332 | case 's': | ||
1333 | shrink = 1; | ||
1334 | break; | ||
1335 | case 'l': | ||
1336 | show_slab = 1; | ||
1337 | break; | ||
1338 | case 't': | ||
1339 | show_track = 1; | ||
1340 | break; | ||
1341 | case 'v': | ||
1342 | validate = 1; | ||
1343 | break; | ||
1344 | case 'z': | ||
1345 | skip_zero = 0; | ||
1346 | break; | ||
1347 | case 'T': | ||
1348 | show_totals = 1; | ||
1349 | break; | ||
1350 | case 'S': | ||
1351 | sort_size = 1; | ||
1352 | break; | ||
1353 | |||
1354 | default: | ||
1355 | fatal("%s: Invalid option '%c'\n", argv[0], optopt); | ||
1356 | |||
1357 | } | ||
1358 | |||
1359 | if (!show_slab && !show_alias && !show_track && !show_report | ||
1360 | && !validate && !shrink && !set_debug && !show_ops) | ||
1361 | show_slab = 1; | ||
1362 | |||
1363 | if (argc > optind) | ||
1364 | pattern_source = argv[optind]; | ||
1365 | else | ||
1366 | pattern_source = ".*"; | ||
1367 | |||
1368 | err = regcomp(&pattern, pattern_source, REG_ICASE|REG_NOSUB); | ||
1369 | if (err) | ||
1370 | fatal("%s: Invalid pattern '%s' code %d\n", | ||
1371 | argv[0], pattern_source, err); | ||
1372 | read_slab_dir(); | ||
1373 | if (show_alias) | ||
1374 | alias(); | ||
1375 | else | ||
1376 | if (show_totals) | ||
1377 | totals(); | ||
1378 | else { | ||
1379 | link_slabs(); | ||
1380 | rename_slabs(); | ||
1381 | sort_slabs(); | ||
1382 | output_slabs(); | ||
1383 | } | ||
1384 | return 0; | ||
1385 | } | ||