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 | } | ||
