diff options
author | Michal Marek <mmarek@suse.cz> | 2010-10-12 09:09:06 -0400 |
---|---|---|
committer | Michal Marek <mmarek@suse.cz> | 2010-10-12 09:09:06 -0400 |
commit | 239060b93bb30a4ad55f1ecaa512464a035cc5ba (patch) | |
tree | 77f79810e57d4fc24356eca0cd6db463e8994128 /tools/perf/util | |
parent | 1408b15b98635a13bad2e2a50b3c2ae2ccdf625b (diff) | |
parent | e9203c988234aa512bd45ca32b52e21c7bbfc414 (diff) |
Merge branch 'kbuild/rc-fixes' into kbuild/kconfig
We need to revert the temporary hack in 71ebc01, hence the merge.
Diffstat (limited to 'tools/perf/util')
47 files changed, 3388 insertions, 1671 deletions
diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c index 70c5cf87d020..e437edb72417 100644 --- a/tools/perf/util/build-id.c +++ b/tools/perf/util/build-id.c | |||
@@ -12,6 +12,7 @@ | |||
12 | #include "event.h" | 12 | #include "event.h" |
13 | #include "symbol.h" | 13 | #include "symbol.h" |
14 | #include <linux/kernel.h> | 14 | #include <linux/kernel.h> |
15 | #include "debug.h" | ||
15 | 16 | ||
16 | static int build_id__mark_dso_hit(event_t *event, struct perf_session *session) | 17 | static int build_id__mark_dso_hit(event_t *event, struct perf_session *session) |
17 | { | 18 | { |
@@ -34,28 +35,43 @@ static int build_id__mark_dso_hit(event_t *event, struct perf_session *session) | |||
34 | return 0; | 35 | return 0; |
35 | } | 36 | } |
36 | 37 | ||
38 | static int event__exit_del_thread(event_t *self, struct perf_session *session) | ||
39 | { | ||
40 | struct thread *thread = perf_session__findnew(session, self->fork.tid); | ||
41 | |||
42 | dump_printf("(%d:%d):(%d:%d)\n", self->fork.pid, self->fork.tid, | ||
43 | self->fork.ppid, self->fork.ptid); | ||
44 | |||
45 | if (thread) { | ||
46 | rb_erase(&thread->rb_node, &session->threads); | ||
47 | session->last_match = NULL; | ||
48 | thread__delete(thread); | ||
49 | } | ||
50 | |||
51 | return 0; | ||
52 | } | ||
53 | |||
37 | struct perf_event_ops build_id__mark_dso_hit_ops = { | 54 | struct perf_event_ops build_id__mark_dso_hit_ops = { |
38 | .sample = build_id__mark_dso_hit, | 55 | .sample = build_id__mark_dso_hit, |
39 | .mmap = event__process_mmap, | 56 | .mmap = event__process_mmap, |
40 | .fork = event__process_task, | 57 | .fork = event__process_task, |
58 | .exit = event__exit_del_thread, | ||
41 | }; | 59 | }; |
42 | 60 | ||
43 | char *dso__build_id_filename(struct dso *self, char *bf, size_t size) | 61 | char *dso__build_id_filename(struct dso *self, char *bf, size_t size) |
44 | { | 62 | { |
45 | char build_id_hex[BUILD_ID_SIZE * 2 + 1]; | 63 | char build_id_hex[BUILD_ID_SIZE * 2 + 1]; |
46 | const char *home; | ||
47 | 64 | ||
48 | if (!self->has_build_id) | 65 | if (!self->has_build_id) |
49 | return NULL; | 66 | return NULL; |
50 | 67 | ||
51 | build_id__sprintf(self->build_id, sizeof(self->build_id), build_id_hex); | 68 | build_id__sprintf(self->build_id, sizeof(self->build_id), build_id_hex); |
52 | home = getenv("HOME"); | ||
53 | if (bf == NULL) { | 69 | if (bf == NULL) { |
54 | if (asprintf(&bf, "%s/%s/.build-id/%.2s/%s", home, | 70 | if (asprintf(&bf, "%s/.build-id/%.2s/%s", buildid_dir, |
55 | DEBUG_CACHE_DIR, build_id_hex, build_id_hex + 2) < 0) | 71 | build_id_hex, build_id_hex + 2) < 0) |
56 | return NULL; | 72 | return NULL; |
57 | } else | 73 | } else |
58 | snprintf(bf, size, "%s/%s/.build-id/%.2s/%s", home, | 74 | snprintf(bf, size, "%s/.build-id/%.2s/%s", buildid_dir, |
59 | DEBUG_CACHE_DIR, build_id_hex, build_id_hex + 2); | 75 | build_id_hex, build_id_hex + 2); |
60 | return bf; | 76 | return bf; |
61 | } | 77 | } |
diff --git a/tools/perf/util/cache.h b/tools/perf/util/cache.h index 65fe664fddf6..27e9ebe4076e 100644 --- a/tools/perf/util/cache.h +++ b/tools/perf/util/cache.h | |||
@@ -23,6 +23,7 @@ extern int perf_config(config_fn_t fn, void *); | |||
23 | extern int perf_config_int(const char *, const char *); | 23 | extern int perf_config_int(const char *, const char *); |
24 | extern int perf_config_bool(const char *, const char *); | 24 | extern int perf_config_bool(const char *, const char *); |
25 | extern int config_error_nonbool(const char *); | 25 | extern int config_error_nonbool(const char *); |
26 | extern const char *perf_config_dirname(const char *, const char *); | ||
26 | 27 | ||
27 | /* pager.c */ | 28 | /* pager.c */ |
28 | extern void setup_pager(void); | 29 | extern void setup_pager(void); |
diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 52c777e451ed..f231f43424d2 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c | |||
@@ -18,7 +18,7 @@ | |||
18 | #include "util.h" | 18 | #include "util.h" |
19 | #include "callchain.h" | 19 | #include "callchain.h" |
20 | 20 | ||
21 | bool ip_callchain__valid(struct ip_callchain *chain, event_t *event) | 21 | bool ip_callchain__valid(struct ip_callchain *chain, const event_t *event) |
22 | { | 22 | { |
23 | unsigned int chain_size = event->header.size; | 23 | unsigned int chain_size = event->header.size; |
24 | chain_size -= (unsigned long)&event->ip.__more_data - (unsigned long)event; | 24 | chain_size -= (unsigned long)&event->ip.__more_data - (unsigned long)event; |
diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index f2e9ee164bd8..624a96c636fd 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h | |||
@@ -63,5 +63,5 @@ int register_callchain_param(struct callchain_param *param); | |||
63 | int append_chain(struct callchain_node *root, struct ip_callchain *chain, | 63 | int append_chain(struct callchain_node *root, struct ip_callchain *chain, |
64 | struct map_symbol *syms, u64 period); | 64 | struct map_symbol *syms, u64 period); |
65 | 65 | ||
66 | bool ip_callchain__valid(struct ip_callchain *chain, event_t *event); | 66 | bool ip_callchain__valid(struct ip_callchain *chain, const event_t *event); |
67 | #endif /* __PERF_CALLCHAIN_H */ | 67 | #endif /* __PERF_CALLCHAIN_H */ |
diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c index dabe892d0e53..e02d78cae70f 100644 --- a/tools/perf/util/config.c +++ b/tools/perf/util/config.c | |||
@@ -11,6 +11,11 @@ | |||
11 | 11 | ||
12 | #define MAXNAME (256) | 12 | #define MAXNAME (256) |
13 | 13 | ||
14 | #define DEBUG_CACHE_DIR ".debug" | ||
15 | |||
16 | |||
17 | char buildid_dir[MAXPATHLEN]; /* root dir for buildid, binary cache */ | ||
18 | |||
14 | static FILE *config_file; | 19 | static FILE *config_file; |
15 | static const char *config_file_name; | 20 | static const char *config_file_name; |
16 | static int config_linenr; | 21 | static int config_linenr; |
@@ -127,7 +132,7 @@ static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) | |||
127 | break; | 132 | break; |
128 | if (!iskeychar(c)) | 133 | if (!iskeychar(c)) |
129 | break; | 134 | break; |
130 | name[len++] = tolower(c); | 135 | name[len++] = c; |
131 | if (len >= MAXNAME) | 136 | if (len >= MAXNAME) |
132 | return -1; | 137 | return -1; |
133 | } | 138 | } |
@@ -327,6 +332,13 @@ int perf_config_bool(const char *name, const char *value) | |||
327 | return !!perf_config_bool_or_int(name, value, &discard); | 332 | return !!perf_config_bool_or_int(name, value, &discard); |
328 | } | 333 | } |
329 | 334 | ||
335 | const char *perf_config_dirname(const char *name, const char *value) | ||
336 | { | ||
337 | if (!name) | ||
338 | return NULL; | ||
339 | return value; | ||
340 | } | ||
341 | |||
330 | static int perf_default_core_config(const char *var __used, const char *value __used) | 342 | static int perf_default_core_config(const char *var __used, const char *value __used) |
331 | { | 343 | { |
332 | /* Add other config variables here and to Documentation/config.txt. */ | 344 | /* Add other config variables here and to Documentation/config.txt. */ |
@@ -428,3 +440,53 @@ int config_error_nonbool(const char *var) | |||
428 | { | 440 | { |
429 | return error("Missing value for '%s'", var); | 441 | return error("Missing value for '%s'", var); |
430 | } | 442 | } |
443 | |||
444 | struct buildid_dir_config { | ||
445 | char *dir; | ||
446 | }; | ||
447 | |||
448 | static int buildid_dir_command_config(const char *var, const char *value, | ||
449 | void *data) | ||
450 | { | ||
451 | struct buildid_dir_config *c = data; | ||
452 | const char *v; | ||
453 | |||
454 | /* same dir for all commands */ | ||
455 | if (!prefixcmp(var, "buildid.") && !strcmp(var + 8, "dir")) { | ||
456 | v = perf_config_dirname(var, value); | ||
457 | if (!v) | ||
458 | return -1; | ||
459 | strncpy(c->dir, v, MAXPATHLEN-1); | ||
460 | c->dir[MAXPATHLEN-1] = '\0'; | ||
461 | } | ||
462 | return 0; | ||
463 | } | ||
464 | |||
465 | static void check_buildid_dir_config(void) | ||
466 | { | ||
467 | struct buildid_dir_config c; | ||
468 | c.dir = buildid_dir; | ||
469 | perf_config(buildid_dir_command_config, &c); | ||
470 | } | ||
471 | |||
472 | void set_buildid_dir(void) | ||
473 | { | ||
474 | buildid_dir[0] = '\0'; | ||
475 | |||
476 | /* try config file */ | ||
477 | check_buildid_dir_config(); | ||
478 | |||
479 | /* default to $HOME/.debug */ | ||
480 | if (buildid_dir[0] == '\0') { | ||
481 | char *v = getenv("HOME"); | ||
482 | if (v) { | ||
483 | snprintf(buildid_dir, MAXPATHLEN-1, "%s/%s", | ||
484 | v, DEBUG_CACHE_DIR); | ||
485 | } else { | ||
486 | strncpy(buildid_dir, DEBUG_CACHE_DIR, MAXPATHLEN-1); | ||
487 | } | ||
488 | buildid_dir[MAXPATHLEN-1] = '\0'; | ||
489 | } | ||
490 | /* for communicating with external commands */ | ||
491 | setenv("PERF_BUILDID_DIR", buildid_dir, 1); | ||
492 | } | ||
diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c index 4e01490e51e5..0f9b8d7a7d7e 100644 --- a/tools/perf/util/cpumap.c +++ b/tools/perf/util/cpumap.c | |||
@@ -20,7 +20,7 @@ static int default_cpu_map(void) | |||
20 | return nr_cpus; | 20 | return nr_cpus; |
21 | } | 21 | } |
22 | 22 | ||
23 | int read_cpu_map(void) | 23 | static int read_all_cpu_map(void) |
24 | { | 24 | { |
25 | FILE *onlnf; | 25 | FILE *onlnf; |
26 | int nr_cpus = 0; | 26 | int nr_cpus = 0; |
@@ -57,3 +57,58 @@ int read_cpu_map(void) | |||
57 | 57 | ||
58 | return default_cpu_map(); | 58 | return default_cpu_map(); |
59 | } | 59 | } |
60 | |||
61 | int read_cpu_map(const char *cpu_list) | ||
62 | { | ||
63 | unsigned long start_cpu, end_cpu = 0; | ||
64 | char *p = NULL; | ||
65 | int i, nr_cpus = 0; | ||
66 | |||
67 | if (!cpu_list) | ||
68 | return read_all_cpu_map(); | ||
69 | |||
70 | if (!isdigit(*cpu_list)) | ||
71 | goto invalid; | ||
72 | |||
73 | while (isdigit(*cpu_list)) { | ||
74 | p = NULL; | ||
75 | start_cpu = strtoul(cpu_list, &p, 0); | ||
76 | if (start_cpu >= INT_MAX | ||
77 | || (*p != '\0' && *p != ',' && *p != '-')) | ||
78 | goto invalid; | ||
79 | |||
80 | if (*p == '-') { | ||
81 | cpu_list = ++p; | ||
82 | p = NULL; | ||
83 | end_cpu = strtoul(cpu_list, &p, 0); | ||
84 | |||
85 | if (end_cpu >= INT_MAX || (*p != '\0' && *p != ',')) | ||
86 | goto invalid; | ||
87 | |||
88 | if (end_cpu < start_cpu) | ||
89 | goto invalid; | ||
90 | } else { | ||
91 | end_cpu = start_cpu; | ||
92 | } | ||
93 | |||
94 | for (; start_cpu <= end_cpu; start_cpu++) { | ||
95 | /* check for duplicates */ | ||
96 | for (i = 0; i < nr_cpus; i++) | ||
97 | if (cpumap[i] == (int)start_cpu) | ||
98 | goto invalid; | ||
99 | |||
100 | assert(nr_cpus < MAX_NR_CPUS); | ||
101 | cpumap[nr_cpus++] = (int)start_cpu; | ||
102 | } | ||
103 | if (*p) | ||
104 | ++p; | ||
105 | |||
106 | cpu_list = p; | ||
107 | } | ||
108 | if (nr_cpus > 0) | ||
109 | return nr_cpus; | ||
110 | |||
111 | return default_cpu_map(); | ||
112 | invalid: | ||
113 | return -1; | ||
114 | } | ||
diff --git a/tools/perf/util/cpumap.h b/tools/perf/util/cpumap.h index 86c78bb33098..3e60f56e490e 100644 --- a/tools/perf/util/cpumap.h +++ b/tools/perf/util/cpumap.h | |||
@@ -1,7 +1,7 @@ | |||
1 | #ifndef __PERF_CPUMAP_H | 1 | #ifndef __PERF_CPUMAP_H |
2 | #define __PERF_CPUMAP_H | 2 | #define __PERF_CPUMAP_H |
3 | 3 | ||
4 | extern int read_cpu_map(void); | 4 | extern int read_cpu_map(const char *cpu_list); |
5 | extern int cpumap[]; | 5 | extern int cpumap[]; |
6 | 6 | ||
7 | #endif /* __PERF_CPUMAP_H */ | 7 | #endif /* __PERF_CPUMAP_H */ |
diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 6cddff2bc970..f9c7e3ad1aa7 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c | |||
@@ -23,7 +23,7 @@ int eprintf(int level, const char *fmt, ...) | |||
23 | if (verbose >= level) { | 23 | if (verbose >= level) { |
24 | va_start(args, fmt); | 24 | va_start(args, fmt); |
25 | if (use_browser > 0) | 25 | if (use_browser > 0) |
26 | ret = browser__show_help(fmt, args); | 26 | ret = ui_helpline__show_help(fmt, args); |
27 | else | 27 | else |
28 | ret = vfprintf(stderr, fmt, args); | 28 | ret = vfprintf(stderr, fmt, args); |
29 | va_end(args); | 29 | va_end(args); |
@@ -86,12 +86,10 @@ void trace_event(event_t *event) | |||
86 | dump_printf_color(" ", color); | 86 | dump_printf_color(" ", color); |
87 | for (j = 0; j < 15-(i & 15); j++) | 87 | for (j = 0; j < 15-(i & 15); j++) |
88 | dump_printf_color(" ", color); | 88 | dump_printf_color(" ", color); |
89 | for (j = 0; j < (i & 15); j++) { | 89 | for (j = i & ~15; j <= i; j++) { |
90 | if (isprint(raw_event[i-15+j])) | 90 | dump_printf_color("%c", color, |
91 | dump_printf_color("%c", color, | 91 | isprint(raw_event[j]) ? |
92 | raw_event[i-15+j]); | 92 | raw_event[j] : '.'); |
93 | else | ||
94 | dump_printf_color(".", color); | ||
95 | } | 93 | } |
96 | dump_printf_color("\n", color); | 94 | dump_printf_color("\n", color); |
97 | } | 95 | } |
diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index 047ac3324ebe..7a17ee061bcb 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h | |||
@@ -14,7 +14,7 @@ void trace_event(event_t *event); | |||
14 | struct ui_progress; | 14 | struct ui_progress; |
15 | 15 | ||
16 | #ifdef NO_NEWT_SUPPORT | 16 | #ifdef NO_NEWT_SUPPORT |
17 | static inline int browser__show_help(const char *format __used, va_list ap __used) | 17 | static inline int ui_helpline__show_help(const char *format __used, va_list ap __used) |
18 | { | 18 | { |
19 | return 0; | 19 | return 0; |
20 | } | 20 | } |
@@ -30,10 +30,9 @@ static inline void ui_progress__update(struct ui_progress *self __used, | |||
30 | 30 | ||
31 | static inline void ui_progress__delete(struct ui_progress *self __used) {} | 31 | static inline void ui_progress__delete(struct ui_progress *self __used) {} |
32 | #else | 32 | #else |
33 | int browser__show_help(const char *format, va_list ap); | 33 | extern char ui_helpline__last_msg[]; |
34 | struct ui_progress *ui_progress__new(const char *title, u64 total); | 34 | int ui_helpline__show_help(const char *format, va_list ap); |
35 | void ui_progress__update(struct ui_progress *self, u64 curr); | 35 | #include "ui/progress.h" |
36 | void ui_progress__delete(struct ui_progress *self); | ||
37 | #endif | 36 | #endif |
38 | 37 | ||
39 | #endif /* __PERF_DEBUG_H */ | 38 | #endif /* __PERF_DEBUG_H */ |
diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index 2fbf6a463c81..dab9e754a281 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c | |||
@@ -151,7 +151,6 @@ static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, | |||
151 | continue; | 151 | continue; |
152 | pbf += n + 3; | 152 | pbf += n + 3; |
153 | if (*pbf == 'x') { /* vm_exec */ | 153 | if (*pbf == 'x') { /* vm_exec */ |
154 | u64 vm_pgoff; | ||
155 | char *execname = strchr(bf, '/'); | 154 | char *execname = strchr(bf, '/'); |
156 | 155 | ||
157 | /* Catch VDSO */ | 156 | /* Catch VDSO */ |
@@ -162,12 +161,7 @@ static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, | |||
162 | continue; | 161 | continue; |
163 | 162 | ||
164 | pbf += 3; | 163 | pbf += 3; |
165 | n = hex2u64(pbf, &vm_pgoff); | 164 | n = hex2u64(pbf, &ev.mmap.pgoff); |
166 | /* pgoff is in bytes, not pages */ | ||
167 | if (n >= 0) | ||
168 | ev.mmap.pgoff = vm_pgoff << getpagesize(); | ||
169 | else | ||
170 | ev.mmap.pgoff = 0; | ||
171 | 165 | ||
172 | size = strlen(execname); | 166 | size = strlen(execname); |
173 | execname[size - 1] = '\0'; /* Remove \n */ | 167 | execname[size - 1] = '\0'; /* Remove \n */ |
@@ -340,30 +334,29 @@ int event__synthesize_kernel_mmap(event__handler_t process, | |||
340 | return process(&ev, session); | 334 | return process(&ev, session); |
341 | } | 335 | } |
342 | 336 | ||
343 | static void thread__comm_adjust(struct thread *self) | 337 | static void thread__comm_adjust(struct thread *self, struct hists *hists) |
344 | { | 338 | { |
345 | char *comm = self->comm; | 339 | char *comm = self->comm; |
346 | 340 | ||
347 | if (!symbol_conf.col_width_list_str && !symbol_conf.field_sep && | 341 | if (!symbol_conf.col_width_list_str && !symbol_conf.field_sep && |
348 | (!symbol_conf.comm_list || | 342 | (!symbol_conf.comm_list || |
349 | strlist__has_entry(symbol_conf.comm_list, comm))) { | 343 | strlist__has_entry(symbol_conf.comm_list, comm))) { |
350 | unsigned int slen = strlen(comm); | 344 | u16 slen = strlen(comm); |
351 | 345 | ||
352 | if (slen > comms__col_width) { | 346 | if (hists__new_col_len(hists, HISTC_COMM, slen)) |
353 | comms__col_width = slen; | 347 | hists__set_col_len(hists, HISTC_THREAD, slen + 6); |
354 | threads__col_width = slen + 6; | ||
355 | } | ||
356 | } | 348 | } |
357 | } | 349 | } |
358 | 350 | ||
359 | static int thread__set_comm_adjust(struct thread *self, const char *comm) | 351 | static int thread__set_comm_adjust(struct thread *self, const char *comm, |
352 | struct hists *hists) | ||
360 | { | 353 | { |
361 | int ret = thread__set_comm(self, comm); | 354 | int ret = thread__set_comm(self, comm); |
362 | 355 | ||
363 | if (ret) | 356 | if (ret) |
364 | return ret; | 357 | return ret; |
365 | 358 | ||
366 | thread__comm_adjust(self); | 359 | thread__comm_adjust(self, hists); |
367 | 360 | ||
368 | return 0; | 361 | return 0; |
369 | } | 362 | } |
@@ -374,7 +367,8 @@ int event__process_comm(event_t *self, struct perf_session *session) | |||
374 | 367 | ||
375 | dump_printf(": %s:%d\n", self->comm.comm, self->comm.tid); | 368 | dump_printf(": %s:%d\n", self->comm.comm, self->comm.tid); |
376 | 369 | ||
377 | if (thread == NULL || thread__set_comm_adjust(thread, self->comm.comm)) { | 370 | if (thread == NULL || thread__set_comm_adjust(thread, self->comm.comm, |
371 | &session->hists)) { | ||
378 | dump_printf("problem processing PERF_RECORD_COMM, skipping event.\n"); | 372 | dump_printf("problem processing PERF_RECORD_COMM, skipping event.\n"); |
379 | return -1; | 373 | return -1; |
380 | } | 374 | } |
@@ -456,6 +450,7 @@ static int event__process_kernel_mmap(event_t *self, | |||
456 | goto out_problem; | 450 | goto out_problem; |
457 | 451 | ||
458 | map->dso->short_name = name; | 452 | map->dso->short_name = name; |
453 | map->dso->sname_alloc = 1; | ||
459 | map->end = map->start + self->mmap.len; | 454 | map->end = map->start + self->mmap.len; |
460 | } else if (is_kernel_mmap) { | 455 | } else if (is_kernel_mmap) { |
461 | const char *symbol_name = (self->mmap.filename + | 456 | const char *symbol_name = (self->mmap.filename + |
@@ -514,12 +509,13 @@ int event__process_mmap(event_t *self, struct perf_session *session) | |||
514 | if (machine == NULL) | 509 | if (machine == NULL) |
515 | goto out_problem; | 510 | goto out_problem; |
516 | thread = perf_session__findnew(session, self->mmap.pid); | 511 | thread = perf_session__findnew(session, self->mmap.pid); |
512 | if (thread == NULL) | ||
513 | goto out_problem; | ||
517 | map = map__new(&machine->user_dsos, self->mmap.start, | 514 | map = map__new(&machine->user_dsos, self->mmap.start, |
518 | self->mmap.len, self->mmap.pgoff, | 515 | self->mmap.len, self->mmap.pgoff, |
519 | self->mmap.pid, self->mmap.filename, | 516 | self->mmap.pid, self->mmap.filename, |
520 | MAP__FUNCTION, session->cwd, session->cwdlen); | 517 | MAP__FUNCTION); |
521 | 518 | if (map == NULL) | |
522 | if (thread == NULL || map == NULL) | ||
523 | goto out_problem; | 519 | goto out_problem; |
524 | 520 | ||
525 | thread__insert_map(thread, map); | 521 | thread__insert_map(thread, map); |
@@ -552,6 +548,26 @@ int event__process_task(event_t *self, struct perf_session *session) | |||
552 | return 0; | 548 | return 0; |
553 | } | 549 | } |
554 | 550 | ||
551 | int event__process(event_t *event, struct perf_session *session) | ||
552 | { | ||
553 | switch (event->header.type) { | ||
554 | case PERF_RECORD_COMM: | ||
555 | event__process_comm(event, session); | ||
556 | break; | ||
557 | case PERF_RECORD_MMAP: | ||
558 | event__process_mmap(event, session); | ||
559 | break; | ||
560 | case PERF_RECORD_FORK: | ||
561 | case PERF_RECORD_EXIT: | ||
562 | event__process_task(event, session); | ||
563 | break; | ||
564 | default: | ||
565 | break; | ||
566 | } | ||
567 | |||
568 | return 0; | ||
569 | } | ||
570 | |||
555 | void thread__find_addr_map(struct thread *self, | 571 | void thread__find_addr_map(struct thread *self, |
556 | struct perf_session *session, u8 cpumode, | 572 | struct perf_session *session, u8 cpumode, |
557 | enum map_type type, pid_t pid, u64 addr, | 573 | enum map_type type, pid_t pid, u64 addr, |
@@ -641,27 +657,49 @@ void thread__find_addr_location(struct thread *self, | |||
641 | al->sym = NULL; | 657 | al->sym = NULL; |
642 | } | 658 | } |
643 | 659 | ||
644 | static void dso__calc_col_width(struct dso *self) | 660 | static void dso__calc_col_width(struct dso *self, struct hists *hists) |
645 | { | 661 | { |
646 | if (!symbol_conf.col_width_list_str && !symbol_conf.field_sep && | 662 | if (!symbol_conf.col_width_list_str && !symbol_conf.field_sep && |
647 | (!symbol_conf.dso_list || | 663 | (!symbol_conf.dso_list || |
648 | strlist__has_entry(symbol_conf.dso_list, self->name))) { | 664 | strlist__has_entry(symbol_conf.dso_list, self->name))) { |
649 | u16 slen = self->short_name_len; | 665 | u16 slen = dso__name_len(self); |
650 | if (verbose) | 666 | hists__new_col_len(hists, HISTC_DSO, slen); |
651 | slen = self->long_name_len; | ||
652 | if (dsos__col_width < slen) | ||
653 | dsos__col_width = slen; | ||
654 | } | 667 | } |
655 | 668 | ||
656 | self->slen_calculated = 1; | 669 | self->slen_calculated = 1; |
657 | } | 670 | } |
658 | 671 | ||
659 | int event__preprocess_sample(const event_t *self, struct perf_session *session, | 672 | int event__preprocess_sample(const event_t *self, struct perf_session *session, |
660 | struct addr_location *al, symbol_filter_t filter) | 673 | struct addr_location *al, struct sample_data *data, |
674 | symbol_filter_t filter) | ||
661 | { | 675 | { |
662 | u8 cpumode = self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; | 676 | u8 cpumode = self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; |
663 | struct thread *thread = perf_session__findnew(session, self->ip.pid); | 677 | struct thread *thread; |
664 | 678 | ||
679 | event__parse_sample(self, session->sample_type, data); | ||
680 | |||
681 | dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld cpu:%d\n", | ||
682 | self->header.misc, data->pid, data->tid, data->ip, | ||
683 | data->period, data->cpu); | ||
684 | |||
685 | if (session->sample_type & PERF_SAMPLE_CALLCHAIN) { | ||
686 | unsigned int i; | ||
687 | |||
688 | dump_printf("... chain: nr:%Lu\n", data->callchain->nr); | ||
689 | |||
690 | if (!ip_callchain__valid(data->callchain, self)) { | ||
691 | pr_debug("call-chain problem with event, " | ||
692 | "skipping it.\n"); | ||
693 | goto out_filtered; | ||
694 | } | ||
695 | |||
696 | if (dump_trace) { | ||
697 | for (i = 0; i < data->callchain->nr; i++) | ||
698 | dump_printf("..... %2d: %016Lx\n", | ||
699 | i, data->callchain->ips[i]); | ||
700 | } | ||
701 | } | ||
702 | thread = perf_session__findnew(session, self->ip.pid); | ||
665 | if (thread == NULL) | 703 | if (thread == NULL) |
666 | return -1; | 704 | return -1; |
667 | 705 | ||
@@ -687,6 +725,7 @@ int event__preprocess_sample(const event_t *self, struct perf_session *session, | |||
687 | al->map ? al->map->dso->long_name : | 725 | al->map ? al->map->dso->long_name : |
688 | al->level == 'H' ? "[hypervisor]" : "<not found>"); | 726 | al->level == 'H' ? "[hypervisor]" : "<not found>"); |
689 | al->sym = NULL; | 727 | al->sym = NULL; |
728 | al->cpu = data->cpu; | ||
690 | 729 | ||
691 | if (al->map) { | 730 | if (al->map) { |
692 | if (symbol_conf.dso_list && | 731 | if (symbol_conf.dso_list && |
@@ -703,16 +742,17 @@ int event__preprocess_sample(const event_t *self, struct perf_session *session, | |||
703 | * sampled. | 742 | * sampled. |
704 | */ | 743 | */ |
705 | if (!sort_dso.elide && !al->map->dso->slen_calculated) | 744 | if (!sort_dso.elide && !al->map->dso->slen_calculated) |
706 | dso__calc_col_width(al->map->dso); | 745 | dso__calc_col_width(al->map->dso, &session->hists); |
707 | 746 | ||
708 | al->sym = map__find_symbol(al->map, al->addr, filter); | 747 | al->sym = map__find_symbol(al->map, al->addr, filter); |
709 | } else { | 748 | } else { |
710 | const unsigned int unresolved_col_width = BITS_PER_LONG / 4; | 749 | const unsigned int unresolved_col_width = BITS_PER_LONG / 4; |
711 | 750 | ||
712 | if (dsos__col_width < unresolved_col_width && | 751 | if (hists__col_len(&session->hists, HISTC_DSO) < unresolved_col_width && |
713 | !symbol_conf.col_width_list_str && !symbol_conf.field_sep && | 752 | !symbol_conf.col_width_list_str && !symbol_conf.field_sep && |
714 | !symbol_conf.dso_list) | 753 | !symbol_conf.dso_list) |
715 | dsos__col_width = unresolved_col_width; | 754 | hists__set_col_len(&session->hists, HISTC_DSO, |
755 | unresolved_col_width); | ||
716 | } | 756 | } |
717 | 757 | ||
718 | if (symbol_conf.sym_list && al->sym && | 758 | if (symbol_conf.sym_list && al->sym && |
@@ -726,9 +766,9 @@ out_filtered: | |||
726 | return 0; | 766 | return 0; |
727 | } | 767 | } |
728 | 768 | ||
729 | int event__parse_sample(event_t *event, u64 type, struct sample_data *data) | 769 | int event__parse_sample(const event_t *event, u64 type, struct sample_data *data) |
730 | { | 770 | { |
731 | u64 *array = event->sample.array; | 771 | const u64 *array = event->sample.array; |
732 | 772 | ||
733 | if (type & PERF_SAMPLE_IP) { | 773 | if (type & PERF_SAMPLE_IP) { |
734 | data->ip = event->ip.ip; | 774 | data->ip = event->ip.ip; |
@@ -767,7 +807,8 @@ int event__parse_sample(event_t *event, u64 type, struct sample_data *data) | |||
767 | u32 *p = (u32 *)array; | 807 | u32 *p = (u32 *)array; |
768 | data->cpu = *p; | 808 | data->cpu = *p; |
769 | array++; | 809 | array++; |
770 | } | 810 | } else |
811 | data->cpu = -1; | ||
771 | 812 | ||
772 | if (type & PERF_SAMPLE_PERIOD) { | 813 | if (type & PERF_SAMPLE_PERIOD) { |
773 | data->period = *array; | 814 | data->period = *array; |
diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index 8577085db067..8e790dae7026 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h | |||
@@ -154,11 +154,13 @@ int event__process_comm(event_t *self, struct perf_session *session); | |||
154 | int event__process_lost(event_t *self, struct perf_session *session); | 154 | int event__process_lost(event_t *self, struct perf_session *session); |
155 | int event__process_mmap(event_t *self, struct perf_session *session); | 155 | int event__process_mmap(event_t *self, struct perf_session *session); |
156 | int event__process_task(event_t *self, struct perf_session *session); | 156 | int event__process_task(event_t *self, struct perf_session *session); |
157 | int event__process(event_t *event, struct perf_session *session); | ||
157 | 158 | ||
158 | struct addr_location; | 159 | struct addr_location; |
159 | int event__preprocess_sample(const event_t *self, struct perf_session *session, | 160 | int event__preprocess_sample(const event_t *self, struct perf_session *session, |
160 | struct addr_location *al, symbol_filter_t filter); | 161 | struct addr_location *al, struct sample_data *data, |
161 | int event__parse_sample(event_t *event, u64 type, struct sample_data *data); | 162 | symbol_filter_t filter); |
163 | int event__parse_sample(const event_t *event, u64 type, struct sample_data *data); | ||
162 | 164 | ||
163 | extern const char *event__name[]; | 165 | extern const char *event__name[]; |
164 | 166 | ||
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 1f62435f96c2..d7e67b167ea3 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c | |||
@@ -16,6 +16,8 @@ | |||
16 | #include "symbol.h" | 16 | #include "symbol.h" |
17 | #include "debug.h" | 17 | #include "debug.h" |
18 | 18 | ||
19 | static bool no_buildid_cache = false; | ||
20 | |||
19 | /* | 21 | /* |
20 | * Create new perf.data header attribute: | 22 | * Create new perf.data header attribute: |
21 | */ | 23 | */ |
@@ -385,8 +387,7 @@ static int perf_session__cache_build_ids(struct perf_session *self) | |||
385 | int ret; | 387 | int ret; |
386 | char debugdir[PATH_MAX]; | 388 | char debugdir[PATH_MAX]; |
387 | 389 | ||
388 | snprintf(debugdir, sizeof(debugdir), "%s/%s", getenv("HOME"), | 390 | snprintf(debugdir, sizeof(debugdir), "%s", buildid_dir); |
389 | DEBUG_CACHE_DIR); | ||
390 | 391 | ||
391 | if (mkdir(debugdir, 0755) != 0 && errno != EEXIST) | 392 | if (mkdir(debugdir, 0755) != 0 && errno != EEXIST) |
392 | return -1; | 393 | return -1; |
@@ -471,7 +472,8 @@ static int perf_header__adds_write(struct perf_header *self, int fd) | |||
471 | } | 472 | } |
472 | buildid_sec->size = lseek(fd, 0, SEEK_CUR) - | 473 | buildid_sec->size = lseek(fd, 0, SEEK_CUR) - |
473 | buildid_sec->offset; | 474 | buildid_sec->offset; |
474 | perf_session__cache_build_ids(session); | 475 | if (!no_buildid_cache) |
476 | perf_session__cache_build_ids(session); | ||
475 | } | 477 | } |
476 | 478 | ||
477 | lseek(fd, sec_start, SEEK_SET); | 479 | lseek(fd, sec_start, SEEK_SET); |
@@ -1190,3 +1192,8 @@ int event__process_build_id(event_t *self, | |||
1190 | session); | 1192 | session); |
1191 | return 0; | 1193 | return 0; |
1192 | } | 1194 | } |
1195 | |||
1196 | void disable_buildid_cache(void) | ||
1197 | { | ||
1198 | no_buildid_cache = true; | ||
1199 | } | ||
diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 784ee0bdda77..be22ae6ef055 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c | |||
@@ -5,11 +5,61 @@ | |||
5 | #include "sort.h" | 5 | #include "sort.h" |
6 | #include <math.h> | 6 | #include <math.h> |
7 | 7 | ||
8 | enum hist_filter { | ||
9 | HIST_FILTER__DSO, | ||
10 | HIST_FILTER__THREAD, | ||
11 | HIST_FILTER__PARENT, | ||
12 | }; | ||
13 | |||
8 | struct callchain_param callchain_param = { | 14 | struct callchain_param callchain_param = { |
9 | .mode = CHAIN_GRAPH_REL, | 15 | .mode = CHAIN_GRAPH_REL, |
10 | .min_percent = 0.5 | 16 | .min_percent = 0.5 |
11 | }; | 17 | }; |
12 | 18 | ||
19 | u16 hists__col_len(struct hists *self, enum hist_column col) | ||
20 | { | ||
21 | return self->col_len[col]; | ||
22 | } | ||
23 | |||
24 | void hists__set_col_len(struct hists *self, enum hist_column col, u16 len) | ||
25 | { | ||
26 | self->col_len[col] = len; | ||
27 | } | ||
28 | |||
29 | bool hists__new_col_len(struct hists *self, enum hist_column col, u16 len) | ||
30 | { | ||
31 | if (len > hists__col_len(self, col)) { | ||
32 | hists__set_col_len(self, col, len); | ||
33 | return true; | ||
34 | } | ||
35 | return false; | ||
36 | } | ||
37 | |||
38 | static void hists__reset_col_len(struct hists *self) | ||
39 | { | ||
40 | enum hist_column col; | ||
41 | |||
42 | for (col = 0; col < HISTC_NR_COLS; ++col) | ||
43 | hists__set_col_len(self, col, 0); | ||
44 | } | ||
45 | |||
46 | static void hists__calc_col_len(struct hists *self, struct hist_entry *h) | ||
47 | { | ||
48 | u16 len; | ||
49 | |||
50 | if (h->ms.sym) | ||
51 | hists__new_col_len(self, HISTC_SYMBOL, h->ms.sym->namelen); | ||
52 | |||
53 | len = thread__comm_len(h->thread); | ||
54 | if (hists__new_col_len(self, HISTC_COMM, len)) | ||
55 | hists__set_col_len(self, HISTC_THREAD, len + 6); | ||
56 | |||
57 | if (h->ms.map) { | ||
58 | len = dso__name_len(h->ms.map->dso); | ||
59 | hists__new_col_len(self, HISTC_DSO, len); | ||
60 | } | ||
61 | } | ||
62 | |||
13 | static void hist_entry__add_cpumode_period(struct hist_entry *self, | 63 | static void hist_entry__add_cpumode_period(struct hist_entry *self, |
14 | unsigned int cpumode, u64 period) | 64 | unsigned int cpumode, u64 period) |
15 | { | 65 | { |
@@ -43,6 +93,8 @@ static struct hist_entry *hist_entry__new(struct hist_entry *template) | |||
43 | if (self != NULL) { | 93 | if (self != NULL) { |
44 | *self = *template; | 94 | *self = *template; |
45 | self->nr_events = 1; | 95 | self->nr_events = 1; |
96 | if (self->ms.map) | ||
97 | self->ms.map->referenced = true; | ||
46 | if (symbol_conf.use_callchain) | 98 | if (symbol_conf.use_callchain) |
47 | callchain_init(self->callchain); | 99 | callchain_init(self->callchain); |
48 | } | 100 | } |
@@ -50,11 +102,19 @@ static struct hist_entry *hist_entry__new(struct hist_entry *template) | |||
50 | return self; | 102 | return self; |
51 | } | 103 | } |
52 | 104 | ||
53 | static void hists__inc_nr_entries(struct hists *self, struct hist_entry *entry) | 105 | static void hists__inc_nr_entries(struct hists *self, struct hist_entry *h) |
54 | { | 106 | { |
55 | if (entry->ms.sym && self->max_sym_namelen < entry->ms.sym->namelen) | 107 | if (!h->filtered) { |
56 | self->max_sym_namelen = entry->ms.sym->namelen; | 108 | hists__calc_col_len(self, h); |
57 | ++self->nr_entries; | 109 | ++self->nr_entries; |
110 | } | ||
111 | } | ||
112 | |||
113 | static u8 symbol__parent_filter(const struct symbol *parent) | ||
114 | { | ||
115 | if (symbol_conf.exclude_other && parent == NULL) | ||
116 | return 1 << HIST_FILTER__PARENT; | ||
117 | return 0; | ||
58 | } | 118 | } |
59 | 119 | ||
60 | struct hist_entry *__hists__add_entry(struct hists *self, | 120 | struct hist_entry *__hists__add_entry(struct hists *self, |
@@ -70,10 +130,12 @@ struct hist_entry *__hists__add_entry(struct hists *self, | |||
70 | .map = al->map, | 130 | .map = al->map, |
71 | .sym = al->sym, | 131 | .sym = al->sym, |
72 | }, | 132 | }, |
133 | .cpu = al->cpu, | ||
73 | .ip = al->addr, | 134 | .ip = al->addr, |
74 | .level = al->level, | 135 | .level = al->level, |
75 | .period = period, | 136 | .period = period, |
76 | .parent = sym_parent, | 137 | .parent = sym_parent, |
138 | .filtered = symbol__parent_filter(sym_parent), | ||
77 | }; | 139 | }; |
78 | int cmp; | 140 | int cmp; |
79 | 141 | ||
@@ -191,7 +253,7 @@ void hists__collapse_resort(struct hists *self) | |||
191 | tmp = RB_ROOT; | 253 | tmp = RB_ROOT; |
192 | next = rb_first(&self->entries); | 254 | next = rb_first(&self->entries); |
193 | self->nr_entries = 0; | 255 | self->nr_entries = 0; |
194 | self->max_sym_namelen = 0; | 256 | hists__reset_col_len(self); |
195 | 257 | ||
196 | while (next) { | 258 | while (next) { |
197 | n = rb_entry(next, struct hist_entry, rb_node); | 259 | n = rb_entry(next, struct hist_entry, rb_node); |
@@ -248,7 +310,7 @@ void hists__output_resort(struct hists *self) | |||
248 | next = rb_first(&self->entries); | 310 | next = rb_first(&self->entries); |
249 | 311 | ||
250 | self->nr_entries = 0; | 312 | self->nr_entries = 0; |
251 | self->max_sym_namelen = 0; | 313 | hists__reset_col_len(self); |
252 | 314 | ||
253 | while (next) { | 315 | while (next) { |
254 | n = rb_entry(next, struct hist_entry, rb_node); | 316 | n = rb_entry(next, struct hist_entry, rb_node); |
@@ -515,8 +577,9 @@ static size_t hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self, | |||
515 | } | 577 | } |
516 | 578 | ||
517 | int hist_entry__snprintf(struct hist_entry *self, char *s, size_t size, | 579 | int hist_entry__snprintf(struct hist_entry *self, char *s, size_t size, |
518 | struct hists *pair_hists, bool show_displacement, | 580 | struct hists *hists, struct hists *pair_hists, |
519 | long displacement, bool color, u64 session_total) | 581 | bool show_displacement, long displacement, |
582 | bool color, u64 session_total) | ||
520 | { | 583 | { |
521 | struct sort_entry *se; | 584 | struct sort_entry *se; |
522 | u64 period, total, period_sys, period_us, period_guest_sys, period_guest_us; | 585 | u64 period, total, period_sys, period_us, period_guest_sys, period_guest_us; |
@@ -620,29 +683,25 @@ int hist_entry__snprintf(struct hist_entry *self, char *s, size_t size, | |||
620 | 683 | ||
621 | ret += snprintf(s + ret, size - ret, "%s", sep ?: " "); | 684 | ret += snprintf(s + ret, size - ret, "%s", sep ?: " "); |
622 | ret += se->se_snprintf(self, s + ret, size - ret, | 685 | ret += se->se_snprintf(self, s + ret, size - ret, |
623 | se->se_width ? *se->se_width : 0); | 686 | hists__col_len(hists, se->se_width_idx)); |
624 | } | 687 | } |
625 | 688 | ||
626 | return ret; | 689 | return ret; |
627 | } | 690 | } |
628 | 691 | ||
629 | int hist_entry__fprintf(struct hist_entry *self, struct hists *pair_hists, | 692 | int hist_entry__fprintf(struct hist_entry *self, struct hists *hists, |
630 | bool show_displacement, long displacement, FILE *fp, | 693 | struct hists *pair_hists, bool show_displacement, |
631 | u64 session_total) | 694 | long displacement, FILE *fp, u64 session_total) |
632 | { | 695 | { |
633 | char bf[512]; | 696 | char bf[512]; |
634 | int ret; | 697 | hist_entry__snprintf(self, bf, sizeof(bf), hists, pair_hists, |
635 | 698 | show_displacement, displacement, | |
636 | ret = hist_entry__snprintf(self, bf, sizeof(bf), pair_hists, | 699 | true, session_total); |
637 | show_displacement, displacement, | ||
638 | true, session_total); | ||
639 | if (!ret) | ||
640 | return 0; | ||
641 | |||
642 | return fprintf(fp, "%s\n", bf); | 700 | return fprintf(fp, "%s\n", bf); |
643 | } | 701 | } |
644 | 702 | ||
645 | static size_t hist_entry__fprintf_callchain(struct hist_entry *self, FILE *fp, | 703 | static size_t hist_entry__fprintf_callchain(struct hist_entry *self, |
704 | struct hists *hists, FILE *fp, | ||
646 | u64 session_total) | 705 | u64 session_total) |
647 | { | 706 | { |
648 | int left_margin = 0; | 707 | int left_margin = 0; |
@@ -650,7 +709,7 @@ static size_t hist_entry__fprintf_callchain(struct hist_entry *self, FILE *fp, | |||
650 | if (sort__first_dimension == SORT_COMM) { | 709 | if (sort__first_dimension == SORT_COMM) { |
651 | struct sort_entry *se = list_first_entry(&hist_entry__sort_list, | 710 | struct sort_entry *se = list_first_entry(&hist_entry__sort_list, |
652 | typeof(*se), list); | 711 | typeof(*se), list); |
653 | left_margin = se->se_width ? *se->se_width : 0; | 712 | left_margin = hists__col_len(hists, se->se_width_idx); |
654 | left_margin -= thread__comm_len(self->thread); | 713 | left_margin -= thread__comm_len(self->thread); |
655 | } | 714 | } |
656 | 715 | ||
@@ -721,17 +780,17 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, | |||
721 | continue; | 780 | continue; |
722 | } | 781 | } |
723 | width = strlen(se->se_header); | 782 | width = strlen(se->se_header); |
724 | if (se->se_width) { | 783 | if (symbol_conf.col_width_list_str) { |
725 | if (symbol_conf.col_width_list_str) { | 784 | if (col_width) { |
726 | if (col_width) { | 785 | hists__set_col_len(self, se->se_width_idx, |
727 | *se->se_width = atoi(col_width); | 786 | atoi(col_width)); |
728 | col_width = strchr(col_width, ','); | 787 | col_width = strchr(col_width, ','); |
729 | if (col_width) | 788 | if (col_width) |
730 | ++col_width; | 789 | ++col_width; |
731 | } | ||
732 | } | 790 | } |
733 | width = *se->se_width = max(*se->se_width, width); | ||
734 | } | 791 | } |
792 | if (!hists__new_col_len(self, se->se_width_idx, width)) | ||
793 | width = hists__col_len(self, se->se_width_idx); | ||
735 | fprintf(fp, " %*s", width, se->se_header); | 794 | fprintf(fp, " %*s", width, se->se_header); |
736 | } | 795 | } |
737 | fprintf(fp, "\n"); | 796 | fprintf(fp, "\n"); |
@@ -754,9 +813,8 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, | |||
754 | continue; | 813 | continue; |
755 | 814 | ||
756 | fprintf(fp, " "); | 815 | fprintf(fp, " "); |
757 | if (se->se_width) | 816 | width = hists__col_len(self, se->se_width_idx); |
758 | width = *se->se_width; | 817 | if (width == 0) |
759 | else | ||
760 | width = strlen(se->se_header); | 818 | width = strlen(se->se_header); |
761 | for (i = 0; i < width; i++) | 819 | for (i = 0; i < width; i++) |
762 | fprintf(fp, "."); | 820 | fprintf(fp, "."); |
@@ -767,7 +825,6 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, | |||
767 | print_entries: | 825 | print_entries: |
768 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { | 826 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { |
769 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | 827 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); |
770 | int cnt; | ||
771 | 828 | ||
772 | if (show_displacement) { | 829 | if (show_displacement) { |
773 | if (h->pair != NULL) | 830 | if (h->pair != NULL) |
@@ -777,17 +834,12 @@ print_entries: | |||
777 | displacement = 0; | 834 | displacement = 0; |
778 | ++position; | 835 | ++position; |
779 | } | 836 | } |
780 | cnt = hist_entry__fprintf(h, pair, show_displacement, | 837 | ret += hist_entry__fprintf(h, self, pair, show_displacement, |
781 | displacement, fp, self->stats.total_period); | 838 | displacement, fp, self->stats.total_period); |
782 | /* Ignore those that didn't match the parent filter */ | ||
783 | if (!cnt) | ||
784 | continue; | ||
785 | |||
786 | ret += cnt; | ||
787 | 839 | ||
788 | if (symbol_conf.use_callchain) | 840 | if (symbol_conf.use_callchain) |
789 | ret += hist_entry__fprintf_callchain(h, fp, self->stats.total_period); | 841 | ret += hist_entry__fprintf_callchain(h, self, fp, |
790 | 842 | self->stats.total_period); | |
791 | if (h->ms.map == NULL && verbose > 1) { | 843 | if (h->ms.map == NULL && verbose > 1) { |
792 | __map_groups__fprintf_maps(&h->thread->mg, | 844 | __map_groups__fprintf_maps(&h->thread->mg, |
793 | MAP__FUNCTION, verbose, fp); | 845 | MAP__FUNCTION, verbose, fp); |
@@ -800,10 +852,52 @@ print_entries: | |||
800 | return ret; | 852 | return ret; |
801 | } | 853 | } |
802 | 854 | ||
803 | enum hist_filter { | 855 | /* |
804 | HIST_FILTER__DSO, | 856 | * See hists__fprintf to match the column widths |
805 | HIST_FILTER__THREAD, | 857 | */ |
806 | }; | 858 | unsigned int hists__sort_list_width(struct hists *self) |
859 | { | ||
860 | struct sort_entry *se; | ||
861 | int ret = 9; /* total % */ | ||
862 | |||
863 | if (symbol_conf.show_cpu_utilization) { | ||
864 | ret += 7; /* count_sys % */ | ||
865 | ret += 6; /* count_us % */ | ||
866 | if (perf_guest) { | ||
867 | ret += 13; /* count_guest_sys % */ | ||
868 | ret += 12; /* count_guest_us % */ | ||
869 | } | ||
870 | } | ||
871 | |||
872 | if (symbol_conf.show_nr_samples) | ||
873 | ret += 11; | ||
874 | |||
875 | list_for_each_entry(se, &hist_entry__sort_list, list) | ||
876 | if (!se->elide) | ||
877 | ret += 2 + hists__col_len(self, se->se_width_idx); | ||
878 | |||
879 | if (verbose) /* Addr + origin */ | ||
880 | ret += 3 + BITS_PER_LONG / 4; | ||
881 | |||
882 | return ret; | ||
883 | } | ||
884 | |||
885 | static void hists__remove_entry_filter(struct hists *self, struct hist_entry *h, | ||
886 | enum hist_filter filter) | ||
887 | { | ||
888 | h->filtered &= ~(1 << filter); | ||
889 | if (h->filtered) | ||
890 | return; | ||
891 | |||
892 | ++self->nr_entries; | ||
893 | if (h->ms.unfolded) | ||
894 | self->nr_entries += h->nr_rows; | ||
895 | h->row_offset = 0; | ||
896 | self->stats.total_period += h->period; | ||
897 | self->stats.nr_events[PERF_RECORD_SAMPLE] += h->nr_events; | ||
898 | |||
899 | hists__calc_col_len(self, h); | ||
900 | } | ||
807 | 901 | ||
808 | void hists__filter_by_dso(struct hists *self, const struct dso *dso) | 902 | void hists__filter_by_dso(struct hists *self, const struct dso *dso) |
809 | { | 903 | { |
@@ -811,7 +905,7 @@ void hists__filter_by_dso(struct hists *self, const struct dso *dso) | |||
811 | 905 | ||
812 | self->nr_entries = self->stats.total_period = 0; | 906 | self->nr_entries = self->stats.total_period = 0; |
813 | self->stats.nr_events[PERF_RECORD_SAMPLE] = 0; | 907 | self->stats.nr_events[PERF_RECORD_SAMPLE] = 0; |
814 | self->max_sym_namelen = 0; | 908 | hists__reset_col_len(self); |
815 | 909 | ||
816 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { | 910 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { |
817 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | 911 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); |
@@ -824,15 +918,7 @@ void hists__filter_by_dso(struct hists *self, const struct dso *dso) | |||
824 | continue; | 918 | continue; |
825 | } | 919 | } |
826 | 920 | ||
827 | h->filtered &= ~(1 << HIST_FILTER__DSO); | 921 | hists__remove_entry_filter(self, h, HIST_FILTER__DSO); |
828 | if (!h->filtered) { | ||
829 | ++self->nr_entries; | ||
830 | self->stats.total_period += h->period; | ||
831 | self->stats.nr_events[PERF_RECORD_SAMPLE] += h->nr_events; | ||
832 | if (h->ms.sym && | ||
833 | self->max_sym_namelen < h->ms.sym->namelen) | ||
834 | self->max_sym_namelen = h->ms.sym->namelen; | ||
835 | } | ||
836 | } | 922 | } |
837 | } | 923 | } |
838 | 924 | ||
@@ -842,7 +928,7 @@ void hists__filter_by_thread(struct hists *self, const struct thread *thread) | |||
842 | 928 | ||
843 | self->nr_entries = self->stats.total_period = 0; | 929 | self->nr_entries = self->stats.total_period = 0; |
844 | self->stats.nr_events[PERF_RECORD_SAMPLE] = 0; | 930 | self->stats.nr_events[PERF_RECORD_SAMPLE] = 0; |
845 | self->max_sym_namelen = 0; | 931 | hists__reset_col_len(self); |
846 | 932 | ||
847 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { | 933 | for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) { |
848 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | 934 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); |
@@ -851,15 +937,8 @@ void hists__filter_by_thread(struct hists *self, const struct thread *thread) | |||
851 | h->filtered |= (1 << HIST_FILTER__THREAD); | 937 | h->filtered |= (1 << HIST_FILTER__THREAD); |
852 | continue; | 938 | continue; |
853 | } | 939 | } |
854 | h->filtered &= ~(1 << HIST_FILTER__THREAD); | 940 | |
855 | if (!h->filtered) { | 941 | hists__remove_entry_filter(self, h, HIST_FILTER__THREAD); |
856 | ++self->nr_entries; | ||
857 | self->stats.total_period += h->period; | ||
858 | self->stats.nr_events[PERF_RECORD_SAMPLE] += h->nr_events; | ||
859 | if (h->ms.sym && | ||
860 | self->max_sym_namelen < h->ms.sym->namelen) | ||
861 | self->max_sym_namelen = h->ms.sym->namelen; | ||
862 | } | ||
863 | } | 942 | } |
864 | } | 943 | } |
865 | 944 | ||
@@ -904,9 +983,9 @@ int hist_entry__inc_addr_samples(struct hist_entry *self, u64 ip) | |||
904 | return 0; | 983 | return 0; |
905 | } | 984 | } |
906 | 985 | ||
907 | static struct objdump_line *objdump_line__new(s64 offset, char *line) | 986 | static struct objdump_line *objdump_line__new(s64 offset, char *line, size_t privsize) |
908 | { | 987 | { |
909 | struct objdump_line *self = malloc(sizeof(*self)); | 988 | struct objdump_line *self = malloc(sizeof(*self) + privsize); |
910 | 989 | ||
911 | if (self != NULL) { | 990 | if (self != NULL) { |
912 | self->offset = offset; | 991 | self->offset = offset; |
@@ -938,7 +1017,7 @@ struct objdump_line *objdump__get_next_ip_line(struct list_head *head, | |||
938 | } | 1017 | } |
939 | 1018 | ||
940 | static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, | 1019 | static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, |
941 | struct list_head *head) | 1020 | struct list_head *head, size_t privsize) |
942 | { | 1021 | { |
943 | struct symbol *sym = self->ms.sym; | 1022 | struct symbol *sym = self->ms.sym; |
944 | struct objdump_line *objdump_line; | 1023 | struct objdump_line *objdump_line; |
@@ -989,7 +1068,7 @@ static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, | |||
989 | offset = -1; | 1068 | offset = -1; |
990 | } | 1069 | } |
991 | 1070 | ||
992 | objdump_line = objdump_line__new(offset, line); | 1071 | objdump_line = objdump_line__new(offset, line, privsize); |
993 | if (objdump_line == NULL) { | 1072 | if (objdump_line == NULL) { |
994 | free(line); | 1073 | free(line); |
995 | return -1; | 1074 | return -1; |
@@ -999,7 +1078,8 @@ static int hist_entry__parse_objdump_line(struct hist_entry *self, FILE *file, | |||
999 | return 0; | 1078 | return 0; |
1000 | } | 1079 | } |
1001 | 1080 | ||
1002 | int hist_entry__annotate(struct hist_entry *self, struct list_head *head) | 1081 | int hist_entry__annotate(struct hist_entry *self, struct list_head *head, |
1082 | size_t privsize) | ||
1003 | { | 1083 | { |
1004 | struct symbol *sym = self->ms.sym; | 1084 | struct symbol *sym = self->ms.sym; |
1005 | struct map *map = self->ms.map; | 1085 | struct map *map = self->ms.map; |
@@ -1052,7 +1132,7 @@ fallback: | |||
1052 | dso, dso->long_name, sym, sym->name); | 1132 | dso, dso->long_name, sym, sym->name); |
1053 | 1133 | ||
1054 | snprintf(command, sizeof(command), | 1134 | snprintf(command, sizeof(command), |
1055 | "objdump --start-address=0x%016Lx --stop-address=0x%016Lx -dS %s|grep -v %s|expand", | 1135 | "objdump --start-address=0x%016Lx --stop-address=0x%016Lx -dS -C %s|grep -v %s|expand", |
1056 | map__rip_2objdump(map, sym->start), | 1136 | map__rip_2objdump(map, sym->start), |
1057 | map__rip_2objdump(map, sym->end), | 1137 | map__rip_2objdump(map, sym->end), |
1058 | filename, filename); | 1138 | filename, filename); |
@@ -1064,7 +1144,7 @@ fallback: | |||
1064 | goto out_free_filename; | 1144 | goto out_free_filename; |
1065 | 1145 | ||
1066 | while (!feof(file)) | 1146 | while (!feof(file)) |
1067 | if (hist_entry__parse_objdump_line(self, file, head) < 0) | 1147 | if (hist_entry__parse_objdump_line(self, file, head, privsize) < 0) |
1068 | break; | 1148 | break; |
1069 | 1149 | ||
1070 | pclose(file); | 1150 | pclose(file); |
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 83fa33a7b38b..587d375d3430 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h | |||
@@ -56,6 +56,16 @@ struct events_stats { | |||
56 | u32 nr_unknown_events; | 56 | u32 nr_unknown_events; |
57 | }; | 57 | }; |
58 | 58 | ||
59 | enum hist_column { | ||
60 | HISTC_SYMBOL, | ||
61 | HISTC_DSO, | ||
62 | HISTC_THREAD, | ||
63 | HISTC_COMM, | ||
64 | HISTC_PARENT, | ||
65 | HISTC_CPU, | ||
66 | HISTC_NR_COLS, /* Last entry */ | ||
67 | }; | ||
68 | |||
59 | struct hists { | 69 | struct hists { |
60 | struct rb_node rb_node; | 70 | struct rb_node rb_node; |
61 | struct rb_root entries; | 71 | struct rb_root entries; |
@@ -64,7 +74,7 @@ struct hists { | |||
64 | u64 config; | 74 | u64 config; |
65 | u64 event_stream; | 75 | u64 event_stream; |
66 | u32 type; | 76 | u32 type; |
67 | u32 max_sym_namelen; | 77 | u16 col_len[HISTC_NR_COLS]; |
68 | }; | 78 | }; |
69 | 79 | ||
70 | struct hist_entry *__hists__add_entry(struct hists *self, | 80 | struct hist_entry *__hists__add_entry(struct hists *self, |
@@ -72,12 +82,13 @@ struct hist_entry *__hists__add_entry(struct hists *self, | |||
72 | struct symbol *parent, u64 period); | 82 | struct symbol *parent, u64 period); |
73 | extern int64_t hist_entry__cmp(struct hist_entry *, struct hist_entry *); | 83 | extern int64_t hist_entry__cmp(struct hist_entry *, struct hist_entry *); |
74 | extern int64_t hist_entry__collapse(struct hist_entry *, struct hist_entry *); | 84 | extern int64_t hist_entry__collapse(struct hist_entry *, struct hist_entry *); |
75 | int hist_entry__fprintf(struct hist_entry *self, struct hists *pair_hists, | 85 | int hist_entry__fprintf(struct hist_entry *self, struct hists *hists, |
76 | bool show_displacement, long displacement, FILE *fp, | 86 | struct hists *pair_hists, bool show_displacement, |
77 | u64 total); | 87 | long displacement, FILE *fp, u64 total); |
78 | int hist_entry__snprintf(struct hist_entry *self, char *bf, size_t size, | 88 | int hist_entry__snprintf(struct hist_entry *self, char *bf, size_t size, |
79 | struct hists *pair_hists, bool show_displacement, | 89 | struct hists *hists, struct hists *pair_hists, |
80 | long displacement, bool color, u64 total); | 90 | bool show_displacement, long displacement, |
91 | bool color, u64 total); | ||
81 | void hist_entry__free(struct hist_entry *); | 92 | void hist_entry__free(struct hist_entry *); |
82 | 93 | ||
83 | void hists__output_resort(struct hists *self); | 94 | void hists__output_resort(struct hists *self); |
@@ -90,11 +101,16 @@ size_t hists__fprintf(struct hists *self, struct hists *pair, | |||
90 | bool show_displacement, FILE *fp); | 101 | bool show_displacement, FILE *fp); |
91 | 102 | ||
92 | int hist_entry__inc_addr_samples(struct hist_entry *self, u64 ip); | 103 | int hist_entry__inc_addr_samples(struct hist_entry *self, u64 ip); |
93 | int hist_entry__annotate(struct hist_entry *self, struct list_head *head); | 104 | int hist_entry__annotate(struct hist_entry *self, struct list_head *head, |
105 | size_t privsize); | ||
94 | 106 | ||
95 | void hists__filter_by_dso(struct hists *self, const struct dso *dso); | 107 | void hists__filter_by_dso(struct hists *self, const struct dso *dso); |
96 | void hists__filter_by_thread(struct hists *self, const struct thread *thread); | 108 | void hists__filter_by_thread(struct hists *self, const struct thread *thread); |
97 | 109 | ||
110 | u16 hists__col_len(struct hists *self, enum hist_column col); | ||
111 | void hists__set_col_len(struct hists *self, enum hist_column col, u16 len); | ||
112 | bool hists__new_col_len(struct hists *self, enum hist_column col, u16 len); | ||
113 | |||
98 | #ifdef NO_NEWT_SUPPORT | 114 | #ifdef NO_NEWT_SUPPORT |
99 | static inline int hists__browse(struct hists *self __used, | 115 | static inline int hists__browse(struct hists *self __used, |
100 | const char *helpline __used, | 116 | const char *helpline __used, |
@@ -126,4 +142,7 @@ int hist_entry__tui_annotate(struct hist_entry *self); | |||
126 | 142 | ||
127 | int hists__tui_browse_tree(struct rb_root *self, const char *help); | 143 | int hists__tui_browse_tree(struct rb_root *self, const char *help); |
128 | #endif | 144 | #endif |
145 | |||
146 | unsigned int hists__sort_list_width(struct hists *self); | ||
147 | |||
129 | #endif /* __PERF_HIST_H */ | 148 | #endif /* __PERF_HIST_H */ |
diff --git a/tools/perf/util/include/linux/list.h b/tools/perf/util/include/linux/list.h index dbe4b814382a..f5ca26e53fbb 100644 --- a/tools/perf/util/include/linux/list.h +++ b/tools/perf/util/include/linux/list.h | |||
@@ -15,4 +15,12 @@ static inline void list_del_range(struct list_head *begin, | |||
15 | begin->prev->next = end->next; | 15 | begin->prev->next = end->next; |
16 | end->next->prev = begin->prev; | 16 | end->next->prev = begin->prev; |
17 | } | 17 | } |
18 | |||
19 | /** | ||
20 | * list_for_each_from - iterate over a list from one of its nodes | ||
21 | * @pos: the &struct list_head to use as a loop cursor, from where to start | ||
22 | * @head: the head for your list. | ||
23 | */ | ||
24 | #define list_for_each_from(pos, head) \ | ||
25 | for (; prefetch(pos->next), pos != (head); pos = pos->next) | ||
18 | #endif | 26 | #endif |
diff --git a/tools/perf/util/include/linux/types.h b/tools/perf/util/include/linux/types.h index 196862a81a21..12de3b8112f9 100644 --- a/tools/perf/util/include/linux/types.h +++ b/tools/perf/util/include/linux/types.h | |||
@@ -6,4 +6,16 @@ | |||
6 | #define DECLARE_BITMAP(name,bits) \ | 6 | #define DECLARE_BITMAP(name,bits) \ |
7 | unsigned long name[BITS_TO_LONGS(bits)] | 7 | unsigned long name[BITS_TO_LONGS(bits)] |
8 | 8 | ||
9 | struct list_head { | ||
10 | struct list_head *next, *prev; | ||
11 | }; | ||
12 | |||
13 | struct hlist_head { | ||
14 | struct hlist_node *first; | ||
15 | }; | ||
16 | |||
17 | struct hlist_node { | ||
18 | struct hlist_node *next, **pprev; | ||
19 | }; | ||
20 | |||
9 | #endif | 21 | #endif |
diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c index e672f2fef65b..3a7eb6ec0eec 100644 --- a/tools/perf/util/map.c +++ b/tools/perf/util/map.c | |||
@@ -17,16 +17,6 @@ static inline int is_anon_memory(const char *filename) | |||
17 | return strcmp(filename, "//anon") == 0; | 17 | return strcmp(filename, "//anon") == 0; |
18 | } | 18 | } |
19 | 19 | ||
20 | static int strcommon(const char *pathname, char *cwd, int cwdlen) | ||
21 | { | ||
22 | int n = 0; | ||
23 | |||
24 | while (n < cwdlen && pathname[n] == cwd[n]) | ||
25 | ++n; | ||
26 | |||
27 | return n; | ||
28 | } | ||
29 | |||
30 | void map__init(struct map *self, enum map_type type, | 20 | void map__init(struct map *self, enum map_type type, |
31 | u64 start, u64 end, u64 pgoff, struct dso *dso) | 21 | u64 start, u64 end, u64 pgoff, struct dso *dso) |
32 | { | 22 | { |
@@ -39,11 +29,12 @@ void map__init(struct map *self, enum map_type type, | |||
39 | self->unmap_ip = map__unmap_ip; | 29 | self->unmap_ip = map__unmap_ip; |
40 | RB_CLEAR_NODE(&self->rb_node); | 30 | RB_CLEAR_NODE(&self->rb_node); |
41 | self->groups = NULL; | 31 | self->groups = NULL; |
32 | self->referenced = false; | ||
42 | } | 33 | } |
43 | 34 | ||
44 | struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, | 35 | struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, |
45 | u64 pgoff, u32 pid, char *filename, | 36 | u64 pgoff, u32 pid, char *filename, |
46 | enum map_type type, char *cwd, int cwdlen) | 37 | enum map_type type) |
47 | { | 38 | { |
48 | struct map *self = malloc(sizeof(*self)); | 39 | struct map *self = malloc(sizeof(*self)); |
49 | 40 | ||
@@ -52,16 +43,6 @@ struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, | |||
52 | struct dso *dso; | 43 | struct dso *dso; |
53 | int anon; | 44 | int anon; |
54 | 45 | ||
55 | if (cwd) { | ||
56 | int n = strcommon(filename, cwd, cwdlen); | ||
57 | |||
58 | if (n == cwdlen) { | ||
59 | snprintf(newfilename, sizeof(newfilename), | ||
60 | ".%s", filename + n); | ||
61 | filename = newfilename; | ||
62 | } | ||
63 | } | ||
64 | |||
65 | anon = is_anon_memory(filename); | 46 | anon = is_anon_memory(filename); |
66 | 47 | ||
67 | if (anon) { | 48 | if (anon) { |
@@ -248,6 +229,39 @@ void map_groups__init(struct map_groups *self) | |||
248 | self->machine = NULL; | 229 | self->machine = NULL; |
249 | } | 230 | } |
250 | 231 | ||
232 | static void maps__delete(struct rb_root *self) | ||
233 | { | ||
234 | struct rb_node *next = rb_first(self); | ||
235 | |||
236 | while (next) { | ||
237 | struct map *pos = rb_entry(next, struct map, rb_node); | ||
238 | |||
239 | next = rb_next(&pos->rb_node); | ||
240 | rb_erase(&pos->rb_node, self); | ||
241 | map__delete(pos); | ||
242 | } | ||
243 | } | ||
244 | |||
245 | static void maps__delete_removed(struct list_head *self) | ||
246 | { | ||
247 | struct map *pos, *n; | ||
248 | |||
249 | list_for_each_entry_safe(pos, n, self, node) { | ||
250 | list_del(&pos->node); | ||
251 | map__delete(pos); | ||
252 | } | ||
253 | } | ||
254 | |||
255 | void map_groups__exit(struct map_groups *self) | ||
256 | { | ||
257 | int i; | ||
258 | |||
259 | for (i = 0; i < MAP__NR_TYPES; ++i) { | ||
260 | maps__delete(&self->maps[i]); | ||
261 | maps__delete_removed(&self->removed_maps[i]); | ||
262 | } | ||
263 | } | ||
264 | |||
251 | void map_groups__flush(struct map_groups *self) | 265 | void map_groups__flush(struct map_groups *self) |
252 | { | 266 | { |
253 | int type; | 267 | int type; |
@@ -374,6 +388,7 @@ int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, | |||
374 | { | 388 | { |
375 | struct rb_root *root = &self->maps[map->type]; | 389 | struct rb_root *root = &self->maps[map->type]; |
376 | struct rb_node *next = rb_first(root); | 390 | struct rb_node *next = rb_first(root); |
391 | int err = 0; | ||
377 | 392 | ||
378 | while (next) { | 393 | while (next) { |
379 | struct map *pos = rb_entry(next, struct map, rb_node); | 394 | struct map *pos = rb_entry(next, struct map, rb_node); |
@@ -390,20 +405,16 @@ int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, | |||
390 | 405 | ||
391 | rb_erase(&pos->rb_node, root); | 406 | rb_erase(&pos->rb_node, root); |
392 | /* | 407 | /* |
393 | * We may have references to this map, for instance in some | ||
394 | * hist_entry instances, so just move them to a separate | ||
395 | * list. | ||
396 | */ | ||
397 | list_add_tail(&pos->node, &self->removed_maps[map->type]); | ||
398 | /* | ||
399 | * Now check if we need to create new maps for areas not | 408 | * Now check if we need to create new maps for areas not |
400 | * overlapped by the new map: | 409 | * overlapped by the new map: |
401 | */ | 410 | */ |
402 | if (map->start > pos->start) { | 411 | if (map->start > pos->start) { |
403 | struct map *before = map__clone(pos); | 412 | struct map *before = map__clone(pos); |
404 | 413 | ||
405 | if (before == NULL) | 414 | if (before == NULL) { |
406 | return -ENOMEM; | 415 | err = -ENOMEM; |
416 | goto move_map; | ||
417 | } | ||
407 | 418 | ||
408 | before->end = map->start - 1; | 419 | before->end = map->start - 1; |
409 | map_groups__insert(self, before); | 420 | map_groups__insert(self, before); |
@@ -414,14 +425,27 @@ int map_groups__fixup_overlappings(struct map_groups *self, struct map *map, | |||
414 | if (map->end < pos->end) { | 425 | if (map->end < pos->end) { |
415 | struct map *after = map__clone(pos); | 426 | struct map *after = map__clone(pos); |
416 | 427 | ||
417 | if (after == NULL) | 428 | if (after == NULL) { |
418 | return -ENOMEM; | 429 | err = -ENOMEM; |
430 | goto move_map; | ||
431 | } | ||
419 | 432 | ||
420 | after->start = map->end + 1; | 433 | after->start = map->end + 1; |
421 | map_groups__insert(self, after); | 434 | map_groups__insert(self, after); |
422 | if (verbose >= 2) | 435 | if (verbose >= 2) |
423 | map__fprintf(after, fp); | 436 | map__fprintf(after, fp); |
424 | } | 437 | } |
438 | move_map: | ||
439 | /* | ||
440 | * If we have references, just move them to a separate list. | ||
441 | */ | ||
442 | if (pos->referenced) | ||
443 | list_add_tail(&pos->node, &self->removed_maps[map->type]); | ||
444 | else | ||
445 | map__delete(pos); | ||
446 | |||
447 | if (err) | ||
448 | return err; | ||
425 | } | 449 | } |
426 | 450 | ||
427 | return 0; | 451 | return 0; |
@@ -493,6 +517,11 @@ void maps__insert(struct rb_root *maps, struct map *map) | |||
493 | rb_insert_color(&map->rb_node, maps); | 517 | rb_insert_color(&map->rb_node, maps); |
494 | } | 518 | } |
495 | 519 | ||
520 | void maps__remove(struct rb_root *self, struct map *map) | ||
521 | { | ||
522 | rb_erase(&map->rb_node, self); | ||
523 | } | ||
524 | |||
496 | struct map *maps__find(struct rb_root *maps, u64 ip) | 525 | struct map *maps__find(struct rb_root *maps, u64 ip) |
497 | { | 526 | { |
498 | struct rb_node **p = &maps->rb_node; | 527 | struct rb_node **p = &maps->rb_node; |
@@ -526,6 +555,31 @@ int machine__init(struct machine *self, const char *root_dir, pid_t pid) | |||
526 | return self->root_dir == NULL ? -ENOMEM : 0; | 555 | return self->root_dir == NULL ? -ENOMEM : 0; |
527 | } | 556 | } |
528 | 557 | ||
558 | static void dsos__delete(struct list_head *self) | ||
559 | { | ||
560 | struct dso *pos, *n; | ||
561 | |||
562 | list_for_each_entry_safe(pos, n, self, node) { | ||
563 | list_del(&pos->node); | ||
564 | dso__delete(pos); | ||
565 | } | ||
566 | } | ||
567 | |||
568 | void machine__exit(struct machine *self) | ||
569 | { | ||
570 | map_groups__exit(&self->kmaps); | ||
571 | dsos__delete(&self->user_dsos); | ||
572 | dsos__delete(&self->kernel_dsos); | ||
573 | free(self->root_dir); | ||
574 | self->root_dir = NULL; | ||
575 | } | ||
576 | |||
577 | void machine__delete(struct machine *self) | ||
578 | { | ||
579 | machine__exit(self); | ||
580 | free(self); | ||
581 | } | ||
582 | |||
529 | struct machine *machines__add(struct rb_root *self, pid_t pid, | 583 | struct machine *machines__add(struct rb_root *self, pid_t pid, |
530 | const char *root_dir) | 584 | const char *root_dir) |
531 | { | 585 | { |
diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h index f39134512829..78575796d5f3 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h | |||
@@ -29,7 +29,8 @@ struct map { | |||
29 | }; | 29 | }; |
30 | u64 start; | 30 | u64 start; |
31 | u64 end; | 31 | u64 end; |
32 | enum map_type type; | 32 | u8 /* enum map_type */ type; |
33 | bool referenced; | ||
33 | u32 priv; | 34 | u32 priv; |
34 | u64 pgoff; | 35 | u64 pgoff; |
35 | 36 | ||
@@ -106,7 +107,7 @@ void map__init(struct map *self, enum map_type type, | |||
106 | u64 start, u64 end, u64 pgoff, struct dso *dso); | 107 | u64 start, u64 end, u64 pgoff, struct dso *dso); |
107 | struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, | 108 | struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, |
108 | u64 pgoff, u32 pid, char *filename, | 109 | u64 pgoff, u32 pid, char *filename, |
109 | enum map_type type, char *cwd, int cwdlen); | 110 | enum map_type type); |
110 | void map__delete(struct map *self); | 111 | void map__delete(struct map *self); |
111 | struct map *map__clone(struct map *self); | 112 | struct map *map__clone(struct map *self); |
112 | int map__overlap(struct map *l, struct map *r); | 113 | int map__overlap(struct map *l, struct map *r); |
@@ -125,8 +126,10 @@ void map__reloc_vmlinux(struct map *self); | |||
125 | size_t __map_groups__fprintf_maps(struct map_groups *self, | 126 | size_t __map_groups__fprintf_maps(struct map_groups *self, |
126 | enum map_type type, int verbose, FILE *fp); | 127 | enum map_type type, int verbose, FILE *fp); |
127 | void maps__insert(struct rb_root *maps, struct map *map); | 128 | void maps__insert(struct rb_root *maps, struct map *map); |
129 | void maps__remove(struct rb_root *self, struct map *map); | ||
128 | struct map *maps__find(struct rb_root *maps, u64 addr); | 130 | struct map *maps__find(struct rb_root *maps, u64 addr); |
129 | void map_groups__init(struct map_groups *self); | 131 | void map_groups__init(struct map_groups *self); |
132 | void map_groups__exit(struct map_groups *self); | ||
130 | int map_groups__clone(struct map_groups *self, | 133 | int map_groups__clone(struct map_groups *self, |
131 | struct map_groups *parent, enum map_type type); | 134 | struct map_groups *parent, enum map_type type); |
132 | size_t map_groups__fprintf(struct map_groups *self, int verbose, FILE *fp); | 135 | size_t map_groups__fprintf(struct map_groups *self, int verbose, FILE *fp); |
@@ -142,6 +145,8 @@ struct machine *machines__find(struct rb_root *self, pid_t pid); | |||
142 | struct machine *machines__findnew(struct rb_root *self, pid_t pid); | 145 | struct machine *machines__findnew(struct rb_root *self, pid_t pid); |
143 | char *machine__mmap_name(struct machine *self, char *bf, size_t size); | 146 | char *machine__mmap_name(struct machine *self, char *bf, size_t size); |
144 | int machine__init(struct machine *self, const char *root_dir, pid_t pid); | 147 | int machine__init(struct machine *self, const char *root_dir, pid_t pid); |
148 | void machine__exit(struct machine *self); | ||
149 | void machine__delete(struct machine *self); | ||
145 | 150 | ||
146 | /* | 151 | /* |
147 | * Default guest kernel is defined by parameter --guestkallsyms | 152 | * Default guest kernel is defined by parameter --guestkallsyms |
@@ -163,6 +168,11 @@ static inline void map_groups__insert(struct map_groups *self, struct map *map) | |||
163 | map->groups = self; | 168 | map->groups = self; |
164 | } | 169 | } |
165 | 170 | ||
171 | static inline void map_groups__remove(struct map_groups *self, struct map *map) | ||
172 | { | ||
173 | maps__remove(&self->maps[map->type], map); | ||
174 | } | ||
175 | |||
166 | static inline struct map *map_groups__find(struct map_groups *self, | 176 | static inline struct map *map_groups__find(struct map_groups *self, |
167 | enum map_type type, u64 addr) | 177 | enum map_type type, u64 addr) |
168 | { | 178 | { |
diff --git a/tools/perf/util/newt.c b/tools/perf/util/newt.c deleted file mode 100644 index 7537ca15900b..000000000000 --- a/tools/perf/util/newt.c +++ /dev/null | |||
@@ -1,1178 +0,0 @@ | |||
1 | #define _GNU_SOURCE | ||
2 | #include <stdio.h> | ||
3 | #undef _GNU_SOURCE | ||
4 | /* | ||
5 | * slang versions <= 2.0.6 have a "#if HAVE_LONG_LONG" that breaks | ||
6 | * the build if it isn't defined. Use the equivalent one that glibc | ||
7 | * has on features.h. | ||
8 | */ | ||
9 | #include <features.h> | ||
10 | #ifndef HAVE_LONG_LONG | ||
11 | #define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG | ||
12 | #endif | ||
13 | #include <slang.h> | ||
14 | #include <stdlib.h> | ||
15 | #include <newt.h> | ||
16 | #include <sys/ttydefaults.h> | ||
17 | |||
18 | #include "cache.h" | ||
19 | #include "hist.h" | ||
20 | #include "pstack.h" | ||
21 | #include "session.h" | ||
22 | #include "sort.h" | ||
23 | #include "symbol.h" | ||
24 | |||
25 | #if SLANG_VERSION < 20104 | ||
26 | #define slsmg_printf(msg, args...) SLsmg_printf((char *)msg, ##args) | ||
27 | #define slsmg_write_nstring(msg, len) SLsmg_write_nstring((char *)msg, len) | ||
28 | #define sltt_set_color(obj, name, fg, bg) SLtt_set_color(obj,(char *)name,\ | ||
29 | (char *)fg, (char *)bg) | ||
30 | #else | ||
31 | #define slsmg_printf SLsmg_printf | ||
32 | #define slsmg_write_nstring SLsmg_write_nstring | ||
33 | #define sltt_set_color SLtt_set_color | ||
34 | #endif | ||
35 | |||
36 | struct ui_progress { | ||
37 | newtComponent form, scale; | ||
38 | }; | ||
39 | |||
40 | struct ui_progress *ui_progress__new(const char *title, u64 total) | ||
41 | { | ||
42 | struct ui_progress *self = malloc(sizeof(*self)); | ||
43 | |||
44 | if (self != NULL) { | ||
45 | int cols; | ||
46 | |||
47 | if (use_browser <= 0) | ||
48 | return self; | ||
49 | newtGetScreenSize(&cols, NULL); | ||
50 | cols -= 4; | ||
51 | newtCenteredWindow(cols, 1, title); | ||
52 | self->form = newtForm(NULL, NULL, 0); | ||
53 | if (self->form == NULL) | ||
54 | goto out_free_self; | ||
55 | self->scale = newtScale(0, 0, cols, total); | ||
56 | if (self->scale == NULL) | ||
57 | goto out_free_form; | ||
58 | newtFormAddComponent(self->form, self->scale); | ||
59 | newtRefresh(); | ||
60 | } | ||
61 | |||
62 | return self; | ||
63 | |||
64 | out_free_form: | ||
65 | newtFormDestroy(self->form); | ||
66 | out_free_self: | ||
67 | free(self); | ||
68 | return NULL; | ||
69 | } | ||
70 | |||
71 | void ui_progress__update(struct ui_progress *self, u64 curr) | ||
72 | { | ||
73 | /* | ||
74 | * FIXME: We should have a per UI backend way of showing progress, | ||
75 | * stdio will just show a percentage as NN%, etc. | ||
76 | */ | ||
77 | if (use_browser <= 0) | ||
78 | return; | ||
79 | newtScaleSet(self->scale, curr); | ||
80 | newtRefresh(); | ||
81 | } | ||
82 | |||
83 | void ui_progress__delete(struct ui_progress *self) | ||
84 | { | ||
85 | if (use_browser > 0) { | ||
86 | newtFormDestroy(self->form); | ||
87 | newtPopWindow(); | ||
88 | } | ||
89 | free(self); | ||
90 | } | ||
91 | |||
92 | static void ui_helpline__pop(void) | ||
93 | { | ||
94 | newtPopHelpLine(); | ||
95 | } | ||
96 | |||
97 | static void ui_helpline__push(const char *msg) | ||
98 | { | ||
99 | newtPushHelpLine(msg); | ||
100 | } | ||
101 | |||
102 | static void ui_helpline__vpush(const char *fmt, va_list ap) | ||
103 | { | ||
104 | char *s; | ||
105 | |||
106 | if (vasprintf(&s, fmt, ap) < 0) | ||
107 | vfprintf(stderr, fmt, ap); | ||
108 | else { | ||
109 | ui_helpline__push(s); | ||
110 | free(s); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | static void ui_helpline__fpush(const char *fmt, ...) | ||
115 | { | ||
116 | va_list ap; | ||
117 | |||
118 | va_start(ap, fmt); | ||
119 | ui_helpline__vpush(fmt, ap); | ||
120 | va_end(ap); | ||
121 | } | ||
122 | |||
123 | static void ui_helpline__puts(const char *msg) | ||
124 | { | ||
125 | ui_helpline__pop(); | ||
126 | ui_helpline__push(msg); | ||
127 | } | ||
128 | |||
129 | static char browser__last_msg[1024]; | ||
130 | |||
131 | int browser__show_help(const char *format, va_list ap) | ||
132 | { | ||
133 | int ret; | ||
134 | static int backlog; | ||
135 | |||
136 | ret = vsnprintf(browser__last_msg + backlog, | ||
137 | sizeof(browser__last_msg) - backlog, format, ap); | ||
138 | backlog += ret; | ||
139 | |||
140 | if (browser__last_msg[backlog - 1] == '\n') { | ||
141 | ui_helpline__puts(browser__last_msg); | ||
142 | newtRefresh(); | ||
143 | backlog = 0; | ||
144 | } | ||
145 | |||
146 | return ret; | ||
147 | } | ||
148 | |||
149 | static void newt_form__set_exit_keys(newtComponent self) | ||
150 | { | ||
151 | newtFormAddHotKey(self, NEWT_KEY_LEFT); | ||
152 | newtFormAddHotKey(self, NEWT_KEY_ESCAPE); | ||
153 | newtFormAddHotKey(self, 'Q'); | ||
154 | newtFormAddHotKey(self, 'q'); | ||
155 | newtFormAddHotKey(self, CTRL('c')); | ||
156 | } | ||
157 | |||
158 | static newtComponent newt_form__new(void) | ||
159 | { | ||
160 | newtComponent self = newtForm(NULL, NULL, 0); | ||
161 | if (self) | ||
162 | newt_form__set_exit_keys(self); | ||
163 | return self; | ||
164 | } | ||
165 | |||
166 | static int popup_menu(int argc, char * const argv[]) | ||
167 | { | ||
168 | struct newtExitStruct es; | ||
169 | int i, rc = -1, max_len = 5; | ||
170 | newtComponent listbox, form = newt_form__new(); | ||
171 | |||
172 | if (form == NULL) | ||
173 | return -1; | ||
174 | |||
175 | listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT); | ||
176 | if (listbox == NULL) | ||
177 | goto out_destroy_form; | ||
178 | |||
179 | newtFormAddComponent(form, listbox); | ||
180 | |||
181 | for (i = 0; i < argc; ++i) { | ||
182 | int len = strlen(argv[i]); | ||
183 | if (len > max_len) | ||
184 | max_len = len; | ||
185 | if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i)) | ||
186 | goto out_destroy_form; | ||
187 | } | ||
188 | |||
189 | newtCenteredWindow(max_len, argc, NULL); | ||
190 | newtFormRun(form, &es); | ||
191 | rc = newtListboxGetCurrent(listbox) - NULL; | ||
192 | if (es.reason == NEWT_EXIT_HOTKEY) | ||
193 | rc = -1; | ||
194 | newtPopWindow(); | ||
195 | out_destroy_form: | ||
196 | newtFormDestroy(form); | ||
197 | return rc; | ||
198 | } | ||
199 | |||
200 | static int ui__help_window(const char *text) | ||
201 | { | ||
202 | struct newtExitStruct es; | ||
203 | newtComponent tb, form = newt_form__new(); | ||
204 | int rc = -1; | ||
205 | int max_len = 0, nr_lines = 0; | ||
206 | const char *t; | ||
207 | |||
208 | if (form == NULL) | ||
209 | return -1; | ||
210 | |||
211 | t = text; | ||
212 | while (1) { | ||
213 | const char *sep = strchr(t, '\n'); | ||
214 | int len; | ||
215 | |||
216 | if (sep == NULL) | ||
217 | sep = strchr(t, '\0'); | ||
218 | len = sep - t; | ||
219 | if (max_len < len) | ||
220 | max_len = len; | ||
221 | ++nr_lines; | ||
222 | if (*sep == '\0') | ||
223 | break; | ||
224 | t = sep + 1; | ||
225 | } | ||
226 | |||
227 | tb = newtTextbox(0, 0, max_len, nr_lines, 0); | ||
228 | if (tb == NULL) | ||
229 | goto out_destroy_form; | ||
230 | |||
231 | newtTextboxSetText(tb, text); | ||
232 | newtFormAddComponent(form, tb); | ||
233 | newtCenteredWindow(max_len, nr_lines, NULL); | ||
234 | newtFormRun(form, &es); | ||
235 | newtPopWindow(); | ||
236 | rc = 0; | ||
237 | out_destroy_form: | ||
238 | newtFormDestroy(form); | ||
239 | return rc; | ||
240 | } | ||
241 | |||
242 | static bool dialog_yesno(const char *msg) | ||
243 | { | ||
244 | /* newtWinChoice should really be accepting const char pointers... */ | ||
245 | char yes[] = "Yes", no[] = "No"; | ||
246 | return newtWinChoice(NULL, yes, no, (char *)msg) == 1; | ||
247 | } | ||
248 | |||
249 | static void ui__error_window(const char *fmt, ...) | ||
250 | { | ||
251 | va_list ap; | ||
252 | |||
253 | va_start(ap, fmt); | ||
254 | newtWinMessagev((char *)"Error", (char *)"Ok", (char *)fmt, ap); | ||
255 | va_end(ap); | ||
256 | } | ||
257 | |||
258 | #define HE_COLORSET_TOP 50 | ||
259 | #define HE_COLORSET_MEDIUM 51 | ||
260 | #define HE_COLORSET_NORMAL 52 | ||
261 | #define HE_COLORSET_SELECTED 53 | ||
262 | #define HE_COLORSET_CODE 54 | ||
263 | |||
264 | static int ui_browser__percent_color(double percent, bool current) | ||
265 | { | ||
266 | if (current) | ||
267 | return HE_COLORSET_SELECTED; | ||
268 | if (percent >= MIN_RED) | ||
269 | return HE_COLORSET_TOP; | ||
270 | if (percent >= MIN_GREEN) | ||
271 | return HE_COLORSET_MEDIUM; | ||
272 | return HE_COLORSET_NORMAL; | ||
273 | } | ||
274 | |||
275 | struct ui_browser { | ||
276 | newtComponent form, sb; | ||
277 | u64 index, first_visible_entry_idx; | ||
278 | void *first_visible_entry, *entries; | ||
279 | u16 top, left, width, height; | ||
280 | void *priv; | ||
281 | u32 nr_entries; | ||
282 | }; | ||
283 | |||
284 | static void ui_browser__refresh_dimensions(struct ui_browser *self) | ||
285 | { | ||
286 | int cols, rows; | ||
287 | newtGetScreenSize(&cols, &rows); | ||
288 | |||
289 | if (self->width > cols - 4) | ||
290 | self->width = cols - 4; | ||
291 | self->height = rows - 5; | ||
292 | if (self->height > self->nr_entries) | ||
293 | self->height = self->nr_entries; | ||
294 | self->top = (rows - self->height) / 2; | ||
295 | self->left = (cols - self->width) / 2; | ||
296 | } | ||
297 | |||
298 | static void ui_browser__reset_index(struct ui_browser *self) | ||
299 | { | ||
300 | self->index = self->first_visible_entry_idx = 0; | ||
301 | self->first_visible_entry = NULL; | ||
302 | } | ||
303 | |||
304 | static int objdump_line__show(struct objdump_line *self, struct list_head *head, | ||
305 | int width, struct hist_entry *he, int len, | ||
306 | bool current_entry) | ||
307 | { | ||
308 | if (self->offset != -1) { | ||
309 | struct symbol *sym = he->ms.sym; | ||
310 | unsigned int hits = 0; | ||
311 | double percent = 0.0; | ||
312 | int color; | ||
313 | struct sym_priv *priv = symbol__priv(sym); | ||
314 | struct sym_ext *sym_ext = priv->ext; | ||
315 | struct sym_hist *h = priv->hist; | ||
316 | s64 offset = self->offset; | ||
317 | struct objdump_line *next = objdump__get_next_ip_line(head, self); | ||
318 | |||
319 | while (offset < (s64)len && | ||
320 | (next == NULL || offset < next->offset)) { | ||
321 | if (sym_ext) { | ||
322 | percent += sym_ext[offset].percent; | ||
323 | } else | ||
324 | hits += h->ip[offset]; | ||
325 | |||
326 | ++offset; | ||
327 | } | ||
328 | |||
329 | if (sym_ext == NULL && h->sum) | ||
330 | percent = 100.0 * hits / h->sum; | ||
331 | |||
332 | color = ui_browser__percent_color(percent, current_entry); | ||
333 | SLsmg_set_color(color); | ||
334 | slsmg_printf(" %7.2f ", percent); | ||
335 | if (!current_entry) | ||
336 | SLsmg_set_color(HE_COLORSET_CODE); | ||
337 | } else { | ||
338 | int color = ui_browser__percent_color(0, current_entry); | ||
339 | SLsmg_set_color(color); | ||
340 | slsmg_write_nstring(" ", 9); | ||
341 | } | ||
342 | |||
343 | SLsmg_write_char(':'); | ||
344 | slsmg_write_nstring(" ", 8); | ||
345 | if (!*self->line) | ||
346 | slsmg_write_nstring(" ", width - 18); | ||
347 | else | ||
348 | slsmg_write_nstring(self->line, width - 18); | ||
349 | |||
350 | return 0; | ||
351 | } | ||
352 | |||
353 | static int ui_browser__refresh_entries(struct ui_browser *self) | ||
354 | { | ||
355 | struct objdump_line *pos; | ||
356 | struct list_head *head = self->entries; | ||
357 | struct hist_entry *he = self->priv; | ||
358 | int row = 0; | ||
359 | int len = he->ms.sym->end - he->ms.sym->start; | ||
360 | |||
361 | if (self->first_visible_entry == NULL || self->first_visible_entry == self->entries) | ||
362 | self->first_visible_entry = head->next; | ||
363 | |||
364 | pos = list_entry(self->first_visible_entry, struct objdump_line, node); | ||
365 | |||
366 | list_for_each_entry_from(pos, head, node) { | ||
367 | bool current_entry = (self->first_visible_entry_idx + row) == self->index; | ||
368 | SLsmg_gotorc(self->top + row, self->left); | ||
369 | objdump_line__show(pos, head, self->width, | ||
370 | he, len, current_entry); | ||
371 | if (++row == self->height) | ||
372 | break; | ||
373 | } | ||
374 | |||
375 | SLsmg_set_color(HE_COLORSET_NORMAL); | ||
376 | SLsmg_fill_region(self->top + row, self->left, | ||
377 | self->height - row, self->width, ' '); | ||
378 | |||
379 | return 0; | ||
380 | } | ||
381 | |||
382 | static int ui_browser__run(struct ui_browser *self, const char *title, | ||
383 | struct newtExitStruct *es) | ||
384 | { | ||
385 | if (self->form) { | ||
386 | newtFormDestroy(self->form); | ||
387 | newtPopWindow(); | ||
388 | } | ||
389 | |||
390 | ui_browser__refresh_dimensions(self); | ||
391 | newtCenteredWindow(self->width + 2, self->height, title); | ||
392 | self->form = newt_form__new(); | ||
393 | if (self->form == NULL) | ||
394 | return -1; | ||
395 | |||
396 | self->sb = newtVerticalScrollbar(self->width + 1, 0, self->height, | ||
397 | HE_COLORSET_NORMAL, | ||
398 | HE_COLORSET_SELECTED); | ||
399 | if (self->sb == NULL) | ||
400 | return -1; | ||
401 | |||
402 | newtFormAddHotKey(self->form, NEWT_KEY_UP); | ||
403 | newtFormAddHotKey(self->form, NEWT_KEY_DOWN); | ||
404 | newtFormAddHotKey(self->form, NEWT_KEY_PGUP); | ||
405 | newtFormAddHotKey(self->form, NEWT_KEY_PGDN); | ||
406 | newtFormAddHotKey(self->form, ' '); | ||
407 | newtFormAddHotKey(self->form, NEWT_KEY_HOME); | ||
408 | newtFormAddHotKey(self->form, NEWT_KEY_END); | ||
409 | newtFormAddHotKey(self->form, NEWT_KEY_TAB); | ||
410 | newtFormAddHotKey(self->form, NEWT_KEY_RIGHT); | ||
411 | |||
412 | if (ui_browser__refresh_entries(self) < 0) | ||
413 | return -1; | ||
414 | newtFormAddComponent(self->form, self->sb); | ||
415 | |||
416 | while (1) { | ||
417 | unsigned int offset; | ||
418 | |||
419 | newtFormRun(self->form, es); | ||
420 | |||
421 | if (es->reason != NEWT_EXIT_HOTKEY) | ||
422 | break; | ||
423 | if (is_exit_key(es->u.key)) | ||
424 | return es->u.key; | ||
425 | switch (es->u.key) { | ||
426 | case NEWT_KEY_DOWN: | ||
427 | if (self->index == self->nr_entries - 1) | ||
428 | break; | ||
429 | ++self->index; | ||
430 | if (self->index == self->first_visible_entry_idx + self->height) { | ||
431 | struct list_head *pos = self->first_visible_entry; | ||
432 | ++self->first_visible_entry_idx; | ||
433 | self->first_visible_entry = pos->next; | ||
434 | } | ||
435 | break; | ||
436 | case NEWT_KEY_UP: | ||
437 | if (self->index == 0) | ||
438 | break; | ||
439 | --self->index; | ||
440 | if (self->index < self->first_visible_entry_idx) { | ||
441 | struct list_head *pos = self->first_visible_entry; | ||
442 | --self->first_visible_entry_idx; | ||
443 | self->first_visible_entry = pos->prev; | ||
444 | } | ||
445 | break; | ||
446 | case NEWT_KEY_PGDN: | ||
447 | case ' ': | ||
448 | if (self->first_visible_entry_idx + self->height > self->nr_entries - 1) | ||
449 | break; | ||
450 | |||
451 | offset = self->height; | ||
452 | if (self->index + offset > self->nr_entries - 1) | ||
453 | offset = self->nr_entries - 1 - self->index; | ||
454 | self->index += offset; | ||
455 | self->first_visible_entry_idx += offset; | ||
456 | |||
457 | while (offset--) { | ||
458 | struct list_head *pos = self->first_visible_entry; | ||
459 | self->first_visible_entry = pos->next; | ||
460 | } | ||
461 | |||
462 | break; | ||
463 | case NEWT_KEY_PGUP: | ||
464 | if (self->first_visible_entry_idx == 0) | ||
465 | break; | ||
466 | |||
467 | if (self->first_visible_entry_idx < self->height) | ||
468 | offset = self->first_visible_entry_idx; | ||
469 | else | ||
470 | offset = self->height; | ||
471 | |||
472 | self->index -= offset; | ||
473 | self->first_visible_entry_idx -= offset; | ||
474 | |||
475 | while (offset--) { | ||
476 | struct list_head *pos = self->first_visible_entry; | ||
477 | self->first_visible_entry = pos->prev; | ||
478 | } | ||
479 | break; | ||
480 | case NEWT_KEY_HOME: | ||
481 | ui_browser__reset_index(self); | ||
482 | break; | ||
483 | case NEWT_KEY_END: { | ||
484 | struct list_head *head = self->entries; | ||
485 | offset = self->height - 1; | ||
486 | |||
487 | if (offset > self->nr_entries) | ||
488 | offset = self->nr_entries; | ||
489 | |||
490 | self->index = self->first_visible_entry_idx = self->nr_entries - 1 - offset; | ||
491 | self->first_visible_entry = head->prev; | ||
492 | while (offset-- != 0) { | ||
493 | struct list_head *pos = self->first_visible_entry; | ||
494 | self->first_visible_entry = pos->prev; | ||
495 | } | ||
496 | } | ||
497 | break; | ||
498 | case NEWT_KEY_RIGHT: | ||
499 | case NEWT_KEY_LEFT: | ||
500 | case NEWT_KEY_TAB: | ||
501 | return es->u.key; | ||
502 | default: | ||
503 | continue; | ||
504 | } | ||
505 | if (ui_browser__refresh_entries(self) < 0) | ||
506 | return -1; | ||
507 | } | ||
508 | return 0; | ||
509 | } | ||
510 | |||
511 | /* | ||
512 | * When debugging newt problems it was useful to be able to "unroll" | ||
513 | * the calls to newtCheckBoxTreeAdd{Array,Item}, so that we can generate | ||
514 | * a source file with the sequence of calls to these methods, to then | ||
515 | * tweak the arrays to get the intended results, so I'm keeping this code | ||
516 | * here, may be useful again in the future. | ||
517 | */ | ||
518 | #undef NEWT_DEBUG | ||
519 | |||
520 | static void newt_checkbox_tree__add(newtComponent tree, const char *str, | ||
521 | void *priv, int *indexes) | ||
522 | { | ||
523 | #ifdef NEWT_DEBUG | ||
524 | /* Print the newtCheckboxTreeAddArray to tinker with its index arrays */ | ||
525 | int i = 0, len = 40 - strlen(str); | ||
526 | |||
527 | fprintf(stderr, | ||
528 | "\tnewtCheckboxTreeAddItem(tree, %*.*s\"%s\", (void *)%p, 0, ", | ||
529 | len, len, " ", str, priv); | ||
530 | while (indexes[i] != NEWT_ARG_LAST) { | ||
531 | if (indexes[i] != NEWT_ARG_APPEND) | ||
532 | fprintf(stderr, " %d,", indexes[i]); | ||
533 | else | ||
534 | fprintf(stderr, " %s,", "NEWT_ARG_APPEND"); | ||
535 | ++i; | ||
536 | } | ||
537 | fprintf(stderr, " %s", " NEWT_ARG_LAST);\n"); | ||
538 | fflush(stderr); | ||
539 | #endif | ||
540 | newtCheckboxTreeAddArray(tree, str, priv, 0, indexes); | ||
541 | } | ||
542 | |||
543 | static char *callchain_list__sym_name(struct callchain_list *self, | ||
544 | char *bf, size_t bfsize) | ||
545 | { | ||
546 | if (self->ms.sym) | ||
547 | return self->ms.sym->name; | ||
548 | |||
549 | snprintf(bf, bfsize, "%#Lx", self->ip); | ||
550 | return bf; | ||
551 | } | ||
552 | |||
553 | static void __callchain__append_graph_browser(struct callchain_node *self, | ||
554 | newtComponent tree, u64 total, | ||
555 | int *indexes, int depth) | ||
556 | { | ||
557 | struct rb_node *node; | ||
558 | u64 new_total, remaining; | ||
559 | int idx = 0; | ||
560 | |||
561 | if (callchain_param.mode == CHAIN_GRAPH_REL) | ||
562 | new_total = self->children_hit; | ||
563 | else | ||
564 | new_total = total; | ||
565 | |||
566 | remaining = new_total; | ||
567 | node = rb_first(&self->rb_root); | ||
568 | while (node) { | ||
569 | struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); | ||
570 | struct rb_node *next = rb_next(node); | ||
571 | u64 cumul = cumul_hits(child); | ||
572 | struct callchain_list *chain; | ||
573 | int first = true, printed = 0; | ||
574 | int chain_idx = -1; | ||
575 | remaining -= cumul; | ||
576 | |||
577 | indexes[depth] = NEWT_ARG_APPEND; | ||
578 | indexes[depth + 1] = NEWT_ARG_LAST; | ||
579 | |||
580 | list_for_each_entry(chain, &child->val, list) { | ||
581 | char ipstr[BITS_PER_LONG / 4 + 1], | ||
582 | *alloc_str = NULL; | ||
583 | const char *str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | ||
584 | |||
585 | if (first) { | ||
586 | double percent = cumul * 100.0 / new_total; | ||
587 | |||
588 | first = false; | ||
589 | if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) | ||
590 | str = "Not enough memory!"; | ||
591 | else | ||
592 | str = alloc_str; | ||
593 | } else { | ||
594 | indexes[depth] = idx; | ||
595 | indexes[depth + 1] = NEWT_ARG_APPEND; | ||
596 | indexes[depth + 2] = NEWT_ARG_LAST; | ||
597 | ++chain_idx; | ||
598 | } | ||
599 | newt_checkbox_tree__add(tree, str, &chain->ms, indexes); | ||
600 | free(alloc_str); | ||
601 | ++printed; | ||
602 | } | ||
603 | |||
604 | indexes[depth] = idx; | ||
605 | if (chain_idx != -1) | ||
606 | indexes[depth + 1] = chain_idx; | ||
607 | if (printed != 0) | ||
608 | ++idx; | ||
609 | __callchain__append_graph_browser(child, tree, new_total, indexes, | ||
610 | depth + (chain_idx != -1 ? 2 : 1)); | ||
611 | node = next; | ||
612 | } | ||
613 | } | ||
614 | |||
615 | static void callchain__append_graph_browser(struct callchain_node *self, | ||
616 | newtComponent tree, u64 total, | ||
617 | int *indexes, int parent_idx) | ||
618 | { | ||
619 | struct callchain_list *chain; | ||
620 | int i = 0; | ||
621 | |||
622 | indexes[1] = NEWT_ARG_APPEND; | ||
623 | indexes[2] = NEWT_ARG_LAST; | ||
624 | |||
625 | list_for_each_entry(chain, &self->val, list) { | ||
626 | char ipstr[BITS_PER_LONG / 4 + 1], *str; | ||
627 | |||
628 | if (chain->ip >= PERF_CONTEXT_MAX) | ||
629 | continue; | ||
630 | |||
631 | if (!i++ && sort__first_dimension == SORT_SYM) | ||
632 | continue; | ||
633 | |||
634 | str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | ||
635 | newt_checkbox_tree__add(tree, str, &chain->ms, indexes); | ||
636 | } | ||
637 | |||
638 | indexes[1] = parent_idx; | ||
639 | indexes[2] = NEWT_ARG_APPEND; | ||
640 | indexes[3] = NEWT_ARG_LAST; | ||
641 | __callchain__append_graph_browser(self, tree, total, indexes, 2); | ||
642 | } | ||
643 | |||
644 | static void hist_entry__append_callchain_browser(struct hist_entry *self, | ||
645 | newtComponent tree, u64 total, int parent_idx) | ||
646 | { | ||
647 | struct rb_node *rb_node; | ||
648 | int indexes[1024] = { [0] = parent_idx, }; | ||
649 | int idx = 0; | ||
650 | struct callchain_node *chain; | ||
651 | |||
652 | rb_node = rb_first(&self->sorted_chain); | ||
653 | while (rb_node) { | ||
654 | chain = rb_entry(rb_node, struct callchain_node, rb_node); | ||
655 | switch (callchain_param.mode) { | ||
656 | case CHAIN_FLAT: | ||
657 | break; | ||
658 | case CHAIN_GRAPH_ABS: /* falldown */ | ||
659 | case CHAIN_GRAPH_REL: | ||
660 | callchain__append_graph_browser(chain, tree, total, indexes, idx++); | ||
661 | break; | ||
662 | case CHAIN_NONE: | ||
663 | default: | ||
664 | break; | ||
665 | } | ||
666 | rb_node = rb_next(rb_node); | ||
667 | } | ||
668 | } | ||
669 | |||
670 | static size_t hist_entry__append_browser(struct hist_entry *self, | ||
671 | newtComponent tree, u64 total) | ||
672 | { | ||
673 | char s[256]; | ||
674 | size_t ret; | ||
675 | |||
676 | if (symbol_conf.exclude_other && !self->parent) | ||
677 | return 0; | ||
678 | |||
679 | ret = hist_entry__snprintf(self, s, sizeof(s), NULL, | ||
680 | false, 0, false, total); | ||
681 | if (symbol_conf.use_callchain) { | ||
682 | int indexes[2]; | ||
683 | |||
684 | indexes[0] = NEWT_ARG_APPEND; | ||
685 | indexes[1] = NEWT_ARG_LAST; | ||
686 | newt_checkbox_tree__add(tree, s, &self->ms, indexes); | ||
687 | } else | ||
688 | newtListboxAppendEntry(tree, s, &self->ms); | ||
689 | |||
690 | return ret; | ||
691 | } | ||
692 | |||
693 | int hist_entry__tui_annotate(struct hist_entry *self) | ||
694 | { | ||
695 | struct ui_browser browser; | ||
696 | struct newtExitStruct es; | ||
697 | struct objdump_line *pos, *n; | ||
698 | LIST_HEAD(head); | ||
699 | int ret; | ||
700 | |||
701 | if (self->ms.sym == NULL) | ||
702 | return -1; | ||
703 | |||
704 | if (self->ms.map->dso->annotate_warned) | ||
705 | return -1; | ||
706 | |||
707 | if (hist_entry__annotate(self, &head) < 0) { | ||
708 | ui__error_window(browser__last_msg); | ||
709 | return -1; | ||
710 | } | ||
711 | |||
712 | ui_helpline__push("Press <- or ESC to exit"); | ||
713 | |||
714 | memset(&browser, 0, sizeof(browser)); | ||
715 | browser.entries = &head; | ||
716 | browser.priv = self; | ||
717 | list_for_each_entry(pos, &head, node) { | ||
718 | size_t line_len = strlen(pos->line); | ||
719 | if (browser.width < line_len) | ||
720 | browser.width = line_len; | ||
721 | ++browser.nr_entries; | ||
722 | } | ||
723 | |||
724 | browser.width += 18; /* Percentage */ | ||
725 | ret = ui_browser__run(&browser, self->ms.sym->name, &es); | ||
726 | newtFormDestroy(browser.form); | ||
727 | newtPopWindow(); | ||
728 | list_for_each_entry_safe(pos, n, &head, node) { | ||
729 | list_del(&pos->node); | ||
730 | objdump_line__free(pos); | ||
731 | } | ||
732 | ui_helpline__pop(); | ||
733 | return ret; | ||
734 | } | ||
735 | |||
736 | static const void *newt__symbol_tree_get_current(newtComponent self) | ||
737 | { | ||
738 | if (symbol_conf.use_callchain) | ||
739 | return newtCheckboxTreeGetCurrent(self); | ||
740 | return newtListboxGetCurrent(self); | ||
741 | } | ||
742 | |||
743 | static void hist_browser__selection(newtComponent self, void *data) | ||
744 | { | ||
745 | const struct map_symbol **symbol_ptr = data; | ||
746 | *symbol_ptr = newt__symbol_tree_get_current(self); | ||
747 | } | ||
748 | |||
749 | struct hist_browser { | ||
750 | newtComponent form, tree; | ||
751 | const struct map_symbol *selection; | ||
752 | }; | ||
753 | |||
754 | static struct hist_browser *hist_browser__new(void) | ||
755 | { | ||
756 | struct hist_browser *self = malloc(sizeof(*self)); | ||
757 | |||
758 | if (self != NULL) | ||
759 | self->form = NULL; | ||
760 | |||
761 | return self; | ||
762 | } | ||
763 | |||
764 | static void hist_browser__delete(struct hist_browser *self) | ||
765 | { | ||
766 | newtFormDestroy(self->form); | ||
767 | newtPopWindow(); | ||
768 | free(self); | ||
769 | } | ||
770 | |||
771 | static int hist_browser__populate(struct hist_browser *self, struct hists *hists, | ||
772 | const char *title) | ||
773 | { | ||
774 | int max_len = 0, idx, cols, rows; | ||
775 | struct ui_progress *progress; | ||
776 | struct rb_node *nd; | ||
777 | u64 curr_hist = 0; | ||
778 | char seq[] = ".", unit; | ||
779 | char str[256]; | ||
780 | unsigned long nr_events = hists->stats.nr_events[PERF_RECORD_SAMPLE]; | ||
781 | |||
782 | if (self->form) { | ||
783 | newtFormDestroy(self->form); | ||
784 | newtPopWindow(); | ||
785 | } | ||
786 | |||
787 | nr_events = convert_unit(nr_events, &unit); | ||
788 | snprintf(str, sizeof(str), "Events: %lu%c ", | ||
789 | nr_events, unit); | ||
790 | newtDrawRootText(0, 0, str); | ||
791 | |||
792 | newtGetScreenSize(NULL, &rows); | ||
793 | |||
794 | if (symbol_conf.use_callchain) | ||
795 | self->tree = newtCheckboxTreeMulti(0, 0, rows - 5, seq, | ||
796 | NEWT_FLAG_SCROLL); | ||
797 | else | ||
798 | self->tree = newtListbox(0, 0, rows - 5, | ||
799 | (NEWT_FLAG_SCROLL | | ||
800 | NEWT_FLAG_RETURNEXIT)); | ||
801 | |||
802 | newtComponentAddCallback(self->tree, hist_browser__selection, | ||
803 | &self->selection); | ||
804 | |||
805 | progress = ui_progress__new("Adding entries to the browser...", | ||
806 | hists->nr_entries); | ||
807 | if (progress == NULL) | ||
808 | return -1; | ||
809 | |||
810 | idx = 0; | ||
811 | for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) { | ||
812 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
813 | int len; | ||
814 | |||
815 | if (h->filtered) | ||
816 | continue; | ||
817 | |||
818 | len = hist_entry__append_browser(h, self->tree, hists->stats.total_period); | ||
819 | if (len > max_len) | ||
820 | max_len = len; | ||
821 | if (symbol_conf.use_callchain) | ||
822 | hist_entry__append_callchain_browser(h, self->tree, | ||
823 | hists->stats.total_period, idx++); | ||
824 | ++curr_hist; | ||
825 | if (curr_hist % 5) | ||
826 | ui_progress__update(progress, curr_hist); | ||
827 | } | ||
828 | |||
829 | ui_progress__delete(progress); | ||
830 | |||
831 | newtGetScreenSize(&cols, &rows); | ||
832 | |||
833 | if (max_len > cols) | ||
834 | max_len = cols - 3; | ||
835 | |||
836 | if (!symbol_conf.use_callchain) | ||
837 | newtListboxSetWidth(self->tree, max_len); | ||
838 | |||
839 | newtCenteredWindow(max_len + (symbol_conf.use_callchain ? 5 : 0), | ||
840 | rows - 5, title); | ||
841 | self->form = newt_form__new(); | ||
842 | if (self->form == NULL) | ||
843 | return -1; | ||
844 | |||
845 | newtFormAddHotKey(self->form, 'A'); | ||
846 | newtFormAddHotKey(self->form, 'a'); | ||
847 | newtFormAddHotKey(self->form, 'D'); | ||
848 | newtFormAddHotKey(self->form, 'd'); | ||
849 | newtFormAddHotKey(self->form, 'T'); | ||
850 | newtFormAddHotKey(self->form, 't'); | ||
851 | newtFormAddHotKey(self->form, '?'); | ||
852 | newtFormAddHotKey(self->form, 'H'); | ||
853 | newtFormAddHotKey(self->form, 'h'); | ||
854 | newtFormAddHotKey(self->form, NEWT_KEY_F1); | ||
855 | newtFormAddHotKey(self->form, NEWT_KEY_RIGHT); | ||
856 | newtFormAddHotKey(self->form, NEWT_KEY_TAB); | ||
857 | newtFormAddHotKey(self->form, NEWT_KEY_UNTAB); | ||
858 | newtFormAddComponents(self->form, self->tree, NULL); | ||
859 | self->selection = newt__symbol_tree_get_current(self->tree); | ||
860 | |||
861 | return 0; | ||
862 | } | ||
863 | |||
864 | static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) | ||
865 | { | ||
866 | int *indexes; | ||
867 | |||
868 | if (!symbol_conf.use_callchain) | ||
869 | goto out; | ||
870 | |||
871 | indexes = newtCheckboxTreeFindItem(self->tree, (void *)self->selection); | ||
872 | if (indexes) { | ||
873 | bool is_hist_entry = indexes[1] == NEWT_ARG_LAST; | ||
874 | free(indexes); | ||
875 | if (is_hist_entry) | ||
876 | goto out; | ||
877 | } | ||
878 | return NULL; | ||
879 | out: | ||
880 | return container_of(self->selection, struct hist_entry, ms); | ||
881 | } | ||
882 | |||
883 | static struct thread *hist_browser__selected_thread(struct hist_browser *self) | ||
884 | { | ||
885 | struct hist_entry *he = hist_browser__selected_entry(self); | ||
886 | return he ? he->thread : NULL; | ||
887 | } | ||
888 | |||
889 | static int hist_browser__title(char *bf, size_t size, const char *ev_name, | ||
890 | const struct dso *dso, const struct thread *thread) | ||
891 | { | ||
892 | int printed = 0; | ||
893 | |||
894 | if (thread) | ||
895 | printed += snprintf(bf + printed, size - printed, | ||
896 | "Thread: %s(%d)", | ||
897 | (thread->comm_set ? thread->comm : ""), | ||
898 | thread->pid); | ||
899 | if (dso) | ||
900 | printed += snprintf(bf + printed, size - printed, | ||
901 | "%sDSO: %s", thread ? " " : "", | ||
902 | dso->short_name); | ||
903 | return printed ?: snprintf(bf, size, "Event: %s", ev_name); | ||
904 | } | ||
905 | |||
906 | int hists__browse(struct hists *self, const char *helpline, const char *ev_name) | ||
907 | { | ||
908 | struct hist_browser *browser = hist_browser__new(); | ||
909 | struct pstack *fstack; | ||
910 | const struct thread *thread_filter = NULL; | ||
911 | const struct dso *dso_filter = NULL; | ||
912 | struct newtExitStruct es; | ||
913 | char msg[160]; | ||
914 | int key = -1; | ||
915 | |||
916 | if (browser == NULL) | ||
917 | return -1; | ||
918 | |||
919 | fstack = pstack__new(2); | ||
920 | if (fstack == NULL) | ||
921 | goto out; | ||
922 | |||
923 | ui_helpline__push(helpline); | ||
924 | |||
925 | hist_browser__title(msg, sizeof(msg), ev_name, | ||
926 | dso_filter, thread_filter); | ||
927 | if (hist_browser__populate(browser, self, msg) < 0) | ||
928 | goto out_free_stack; | ||
929 | |||
930 | while (1) { | ||
931 | const struct thread *thread; | ||
932 | const struct dso *dso; | ||
933 | char *options[16]; | ||
934 | int nr_options = 0, choice = 0, i, | ||
935 | annotate = -2, zoom_dso = -2, zoom_thread = -2; | ||
936 | |||
937 | newtFormRun(browser->form, &es); | ||
938 | |||
939 | thread = hist_browser__selected_thread(browser); | ||
940 | dso = browser->selection->map ? browser->selection->map->dso : NULL; | ||
941 | |||
942 | if (es.reason == NEWT_EXIT_HOTKEY) { | ||
943 | key = es.u.key; | ||
944 | |||
945 | switch (key) { | ||
946 | case NEWT_KEY_F1: | ||
947 | goto do_help; | ||
948 | case NEWT_KEY_TAB: | ||
949 | case NEWT_KEY_UNTAB: | ||
950 | /* | ||
951 | * Exit the browser, let hists__browser_tree | ||
952 | * go to the next or previous | ||
953 | */ | ||
954 | goto out_free_stack; | ||
955 | default:; | ||
956 | } | ||
957 | |||
958 | key = toupper(key); | ||
959 | switch (key) { | ||
960 | case 'A': | ||
961 | if (browser->selection->map == NULL && | ||
962 | browser->selection->map->dso->annotate_warned) | ||
963 | continue; | ||
964 | goto do_annotate; | ||
965 | case 'D': | ||
966 | goto zoom_dso; | ||
967 | case 'T': | ||
968 | goto zoom_thread; | ||
969 | case 'H': | ||
970 | case '?': | ||
971 | do_help: | ||
972 | ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n" | ||
973 | "<- Zoom out\n" | ||
974 | "a Annotate current symbol\n" | ||
975 | "h/?/F1 Show this window\n" | ||
976 | "d Zoom into current DSO\n" | ||
977 | "t Zoom into current Thread\n" | ||
978 | "q/CTRL+C Exit browser"); | ||
979 | continue; | ||
980 | default:; | ||
981 | } | ||
982 | if (is_exit_key(key)) { | ||
983 | if (key == NEWT_KEY_ESCAPE) { | ||
984 | if (dialog_yesno("Do you really want to exit?")) | ||
985 | break; | ||
986 | else | ||
987 | continue; | ||
988 | } else | ||
989 | break; | ||
990 | } | ||
991 | |||
992 | if (es.u.key == NEWT_KEY_LEFT) { | ||
993 | const void *top; | ||
994 | |||
995 | if (pstack__empty(fstack)) | ||
996 | continue; | ||
997 | top = pstack__pop(fstack); | ||
998 | if (top == &dso_filter) | ||
999 | goto zoom_out_dso; | ||
1000 | if (top == &thread_filter) | ||
1001 | goto zoom_out_thread; | ||
1002 | continue; | ||
1003 | } | ||
1004 | } | ||
1005 | |||
1006 | if (browser->selection->sym != NULL && | ||
1007 | !browser->selection->map->dso->annotate_warned && | ||
1008 | asprintf(&options[nr_options], "Annotate %s", | ||
1009 | browser->selection->sym->name) > 0) | ||
1010 | annotate = nr_options++; | ||
1011 | |||
1012 | if (thread != NULL && | ||
1013 | asprintf(&options[nr_options], "Zoom %s %s(%d) thread", | ||
1014 | (thread_filter ? "out of" : "into"), | ||
1015 | (thread->comm_set ? thread->comm : ""), | ||
1016 | thread->pid) > 0) | ||
1017 | zoom_thread = nr_options++; | ||
1018 | |||
1019 | if (dso != NULL && | ||
1020 | asprintf(&options[nr_options], "Zoom %s %s DSO", | ||
1021 | (dso_filter ? "out of" : "into"), | ||
1022 | (dso->kernel ? "the Kernel" : dso->short_name)) > 0) | ||
1023 | zoom_dso = nr_options++; | ||
1024 | |||
1025 | options[nr_options++] = (char *)"Exit"; | ||
1026 | |||
1027 | choice = popup_menu(nr_options, options); | ||
1028 | |||
1029 | for (i = 0; i < nr_options - 1; ++i) | ||
1030 | free(options[i]); | ||
1031 | |||
1032 | if (choice == nr_options - 1) | ||
1033 | break; | ||
1034 | |||
1035 | if (choice == -1) | ||
1036 | continue; | ||
1037 | |||
1038 | if (choice == annotate) { | ||
1039 | struct hist_entry *he; | ||
1040 | do_annotate: | ||
1041 | if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) { | ||
1042 | browser->selection->map->dso->annotate_warned = 1; | ||
1043 | ui_helpline__puts("No vmlinux file found, can't " | ||
1044 | "annotate with just a " | ||
1045 | "kallsyms file"); | ||
1046 | continue; | ||
1047 | } | ||
1048 | |||
1049 | he = hist_browser__selected_entry(browser); | ||
1050 | if (he == NULL) | ||
1051 | continue; | ||
1052 | |||
1053 | hist_entry__tui_annotate(he); | ||
1054 | } else if (choice == zoom_dso) { | ||
1055 | zoom_dso: | ||
1056 | if (dso_filter) { | ||
1057 | pstack__remove(fstack, &dso_filter); | ||
1058 | zoom_out_dso: | ||
1059 | ui_helpline__pop(); | ||
1060 | dso_filter = NULL; | ||
1061 | } else { | ||
1062 | if (dso == NULL) | ||
1063 | continue; | ||
1064 | ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"", | ||
1065 | dso->kernel ? "the Kernel" : dso->short_name); | ||
1066 | dso_filter = dso; | ||
1067 | pstack__push(fstack, &dso_filter); | ||
1068 | } | ||
1069 | hists__filter_by_dso(self, dso_filter); | ||
1070 | hist_browser__title(msg, sizeof(msg), ev_name, | ||
1071 | dso_filter, thread_filter); | ||
1072 | if (hist_browser__populate(browser, self, msg) < 0) | ||
1073 | goto out; | ||
1074 | } else if (choice == zoom_thread) { | ||
1075 | zoom_thread: | ||
1076 | if (thread_filter) { | ||
1077 | pstack__remove(fstack, &thread_filter); | ||
1078 | zoom_out_thread: | ||
1079 | ui_helpline__pop(); | ||
1080 | thread_filter = NULL; | ||
1081 | } else { | ||
1082 | ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", | ||
1083 | thread->comm_set ? thread->comm : "", | ||
1084 | thread->pid); | ||
1085 | thread_filter = thread; | ||
1086 | pstack__push(fstack, &thread_filter); | ||
1087 | } | ||
1088 | hists__filter_by_thread(self, thread_filter); | ||
1089 | hist_browser__title(msg, sizeof(msg), ev_name, | ||
1090 | dso_filter, thread_filter); | ||
1091 | if (hist_browser__populate(browser, self, msg) < 0) | ||
1092 | goto out; | ||
1093 | } | ||
1094 | } | ||
1095 | out_free_stack: | ||
1096 | pstack__delete(fstack); | ||
1097 | out: | ||
1098 | hist_browser__delete(browser); | ||
1099 | return key; | ||
1100 | } | ||
1101 | |||
1102 | int hists__tui_browse_tree(struct rb_root *self, const char *help) | ||
1103 | { | ||
1104 | struct rb_node *first = rb_first(self), *nd = first, *next; | ||
1105 | int key = 0; | ||
1106 | |||
1107 | while (nd) { | ||
1108 | struct hists *hists = rb_entry(nd, struct hists, rb_node); | ||
1109 | const char *ev_name = __event_name(hists->type, hists->config); | ||
1110 | |||
1111 | key = hists__browse(hists, help, ev_name); | ||
1112 | |||
1113 | if (is_exit_key(key)) | ||
1114 | break; | ||
1115 | |||
1116 | switch (key) { | ||
1117 | case NEWT_KEY_TAB: | ||
1118 | next = rb_next(nd); | ||
1119 | if (next) | ||
1120 | nd = next; | ||
1121 | break; | ||
1122 | case NEWT_KEY_UNTAB: | ||
1123 | if (nd == first) | ||
1124 | continue; | ||
1125 | nd = rb_prev(nd); | ||
1126 | default: | ||
1127 | break; | ||
1128 | } | ||
1129 | } | ||
1130 | |||
1131 | return key; | ||
1132 | } | ||
1133 | |||
1134 | static struct newtPercentTreeColors { | ||
1135 | const char *topColorFg, *topColorBg; | ||
1136 | const char *mediumColorFg, *mediumColorBg; | ||
1137 | const char *normalColorFg, *normalColorBg; | ||
1138 | const char *selColorFg, *selColorBg; | ||
1139 | const char *codeColorFg, *codeColorBg; | ||
1140 | } defaultPercentTreeColors = { | ||
1141 | "red", "lightgray", | ||
1142 | "green", "lightgray", | ||
1143 | "black", "lightgray", | ||
1144 | "lightgray", "magenta", | ||
1145 | "blue", "lightgray", | ||
1146 | }; | ||
1147 | |||
1148 | void setup_browser(void) | ||
1149 | { | ||
1150 | struct newtPercentTreeColors *c = &defaultPercentTreeColors; | ||
1151 | |||
1152 | if (!isatty(1) || !use_browser || dump_trace) { | ||
1153 | use_browser = 0; | ||
1154 | setup_pager(); | ||
1155 | return; | ||
1156 | } | ||
1157 | |||
1158 | use_browser = 1; | ||
1159 | newtInit(); | ||
1160 | newtCls(); | ||
1161 | ui_helpline__puts(" "); | ||
1162 | sltt_set_color(HE_COLORSET_TOP, NULL, c->topColorFg, c->topColorBg); | ||
1163 | sltt_set_color(HE_COLORSET_MEDIUM, NULL, c->mediumColorFg, c->mediumColorBg); | ||
1164 | sltt_set_color(HE_COLORSET_NORMAL, NULL, c->normalColorFg, c->normalColorBg); | ||
1165 | sltt_set_color(HE_COLORSET_SELECTED, NULL, c->selColorFg, c->selColorBg); | ||
1166 | sltt_set_color(HE_COLORSET_CODE, NULL, c->codeColorFg, c->codeColorBg); | ||
1167 | } | ||
1168 | |||
1169 | void exit_browser(bool wait_for_ok) | ||
1170 | { | ||
1171 | if (use_browser > 0) { | ||
1172 | if (wait_for_ok) { | ||
1173 | char title[] = "Fatal Error", ok[] = "Ok"; | ||
1174 | newtWinMessage(title, ok, browser__last_msg); | ||
1175 | } | ||
1176 | newtFinished(); | ||
1177 | } | ||
1178 | } | ||
diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 9bf0f402ca73..4af5bd59cfd1 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c | |||
@@ -602,8 +602,15 @@ parse_breakpoint_event(const char **strp, struct perf_event_attr *attr) | |||
602 | return EVT_FAILED; | 602 | return EVT_FAILED; |
603 | } | 603 | } |
604 | 604 | ||
605 | /* We should find a nice way to override the access type */ | 605 | /* |
606 | attr->bp_len = HW_BREAKPOINT_LEN_4; | 606 | * We should find a nice way to override the access length |
607 | * Provide some defaults for now | ||
608 | */ | ||
609 | if (attr->bp_type == HW_BREAKPOINT_X) | ||
610 | attr->bp_len = sizeof(long); | ||
611 | else | ||
612 | attr->bp_len = HW_BREAKPOINT_LEN_4; | ||
613 | |||
607 | attr->type = PERF_TYPE_BREAKPOINT; | 614 | attr->type = PERF_TYPE_BREAKPOINT; |
608 | 615 | ||
609 | return EVT_HANDLED; | 616 | return EVT_HANDLED; |
diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 914c67095d96..e72f05c3bef0 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * probe-event.c : perf-probe definition to kprobe_events format converter | 2 | * probe-event.c : perf-probe definition to probe_events format converter |
3 | * | 3 | * |
4 | * Written by Masami Hiramatsu <mhiramat@redhat.com> | 4 | * Written by Masami Hiramatsu <mhiramat@redhat.com> |
5 | * | 5 | * |
@@ -120,8 +120,11 @@ static int open_vmlinux(void) | |||
120 | return open(machine.vmlinux_maps[MAP__FUNCTION]->dso->long_name, O_RDONLY); | 120 | return open(machine.vmlinux_maps[MAP__FUNCTION]->dso->long_name, O_RDONLY); |
121 | } | 121 | } |
122 | 122 | ||
123 | /* Convert trace point to probe point with debuginfo */ | 123 | /* |
124 | static int convert_to_perf_probe_point(struct kprobe_trace_point *tp, | 124 | * Convert trace point to probe point with debuginfo |
125 | * Currently only handles kprobes. | ||
126 | */ | ||
127 | static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, | ||
125 | struct perf_probe_point *pp) | 128 | struct perf_probe_point *pp) |
126 | { | 129 | { |
127 | struct symbol *sym; | 130 | struct symbol *sym; |
@@ -151,8 +154,8 @@ static int convert_to_perf_probe_point(struct kprobe_trace_point *tp, | |||
151 | } | 154 | } |
152 | 155 | ||
153 | /* Try to find perf_probe_event with debuginfo */ | 156 | /* Try to find perf_probe_event with debuginfo */ |
154 | static int try_to_find_kprobe_trace_events(struct perf_probe_event *pev, | 157 | static int try_to_find_probe_trace_events(struct perf_probe_event *pev, |
155 | struct kprobe_trace_event **tevs, | 158 | struct probe_trace_event **tevs, |
156 | int max_tevs) | 159 | int max_tevs) |
157 | { | 160 | { |
158 | bool need_dwarf = perf_probe_event_need_dwarf(pev); | 161 | bool need_dwarf = perf_probe_event_need_dwarf(pev); |
@@ -169,11 +172,11 @@ static int try_to_find_kprobe_trace_events(struct perf_probe_event *pev, | |||
169 | } | 172 | } |
170 | 173 | ||
171 | /* Searching trace events corresponding to probe event */ | 174 | /* Searching trace events corresponding to probe event */ |
172 | ntevs = find_kprobe_trace_events(fd, pev, tevs, max_tevs); | 175 | ntevs = find_probe_trace_events(fd, pev, tevs, max_tevs); |
173 | close(fd); | 176 | close(fd); |
174 | 177 | ||
175 | if (ntevs > 0) { /* Succeeded to find trace events */ | 178 | if (ntevs > 0) { /* Succeeded to find trace events */ |
176 | pr_debug("find %d kprobe_trace_events.\n", ntevs); | 179 | pr_debug("find %d probe_trace_events.\n", ntevs); |
177 | return ntevs; | 180 | return ntevs; |
178 | } | 181 | } |
179 | 182 | ||
@@ -195,6 +198,65 @@ static int try_to_find_kprobe_trace_events(struct perf_probe_event *pev, | |||
195 | return ntevs; | 198 | return ntevs; |
196 | } | 199 | } |
197 | 200 | ||
201 | /* | ||
202 | * Find a src file from a DWARF tag path. Prepend optional source path prefix | ||
203 | * and chop off leading directories that do not exist. Result is passed back as | ||
204 | * a newly allocated path on success. | ||
205 | * Return 0 if file was found and readable, -errno otherwise. | ||
206 | */ | ||
207 | static int get_real_path(const char *raw_path, const char *comp_dir, | ||
208 | char **new_path) | ||
209 | { | ||
210 | const char *prefix = symbol_conf.source_prefix; | ||
211 | |||
212 | if (!prefix) { | ||
213 | if (raw_path[0] != '/' && comp_dir) | ||
214 | /* If not an absolute path, try to use comp_dir */ | ||
215 | prefix = comp_dir; | ||
216 | else { | ||
217 | if (access(raw_path, R_OK) == 0) { | ||
218 | *new_path = strdup(raw_path); | ||
219 | return 0; | ||
220 | } else | ||
221 | return -errno; | ||
222 | } | ||
223 | } | ||
224 | |||
225 | *new_path = malloc((strlen(prefix) + strlen(raw_path) + 2)); | ||
226 | if (!*new_path) | ||
227 | return -ENOMEM; | ||
228 | |||
229 | for (;;) { | ||
230 | sprintf(*new_path, "%s/%s", prefix, raw_path); | ||
231 | |||
232 | if (access(*new_path, R_OK) == 0) | ||
233 | return 0; | ||
234 | |||
235 | if (!symbol_conf.source_prefix) | ||
236 | /* In case of searching comp_dir, don't retry */ | ||
237 | return -errno; | ||
238 | |||
239 | switch (errno) { | ||
240 | case ENAMETOOLONG: | ||
241 | case ENOENT: | ||
242 | case EROFS: | ||
243 | case EFAULT: | ||
244 | raw_path = strchr(++raw_path, '/'); | ||
245 | if (!raw_path) { | ||
246 | free(*new_path); | ||
247 | *new_path = NULL; | ||
248 | return -ENOENT; | ||
249 | } | ||
250 | continue; | ||
251 | |||
252 | default: | ||
253 | free(*new_path); | ||
254 | *new_path = NULL; | ||
255 | return -errno; | ||
256 | } | ||
257 | } | ||
258 | } | ||
259 | |||
198 | #define LINEBUF_SIZE 256 | 260 | #define LINEBUF_SIZE 256 |
199 | #define NR_ADDITIONAL_LINES 2 | 261 | #define NR_ADDITIONAL_LINES 2 |
200 | 262 | ||
@@ -244,6 +306,7 @@ int show_line_range(struct line_range *lr) | |||
244 | struct line_node *ln; | 306 | struct line_node *ln; |
245 | FILE *fp; | 307 | FILE *fp; |
246 | int fd, ret; | 308 | int fd, ret; |
309 | char *tmp; | ||
247 | 310 | ||
248 | /* Search a line range */ | 311 | /* Search a line range */ |
249 | ret = init_vmlinux(); | 312 | ret = init_vmlinux(); |
@@ -266,6 +329,15 @@ int show_line_range(struct line_range *lr) | |||
266 | return ret; | 329 | return ret; |
267 | } | 330 | } |
268 | 331 | ||
332 | /* Convert source file path */ | ||
333 | tmp = lr->path; | ||
334 | ret = get_real_path(tmp, lr->comp_dir, &lr->path); | ||
335 | free(tmp); /* Free old path */ | ||
336 | if (ret < 0) { | ||
337 | pr_warning("Failed to find source file. (%d)\n", ret); | ||
338 | return ret; | ||
339 | } | ||
340 | |||
269 | setup_pager(); | 341 | setup_pager(); |
270 | 342 | ||
271 | if (lr->function) | 343 | if (lr->function) |
@@ -308,8 +380,8 @@ end: | |||
308 | 380 | ||
309 | #else /* !DWARF_SUPPORT */ | 381 | #else /* !DWARF_SUPPORT */ |
310 | 382 | ||
311 | static int convert_to_perf_probe_point(struct kprobe_trace_point *tp, | 383 | static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, |
312 | struct perf_probe_point *pp) | 384 | struct perf_probe_point *pp) |
313 | { | 385 | { |
314 | pp->function = strdup(tp->symbol); | 386 | pp->function = strdup(tp->symbol); |
315 | if (pp->function == NULL) | 387 | if (pp->function == NULL) |
@@ -320,8 +392,8 @@ static int convert_to_perf_probe_point(struct kprobe_trace_point *tp, | |||
320 | return 0; | 392 | return 0; |
321 | } | 393 | } |
322 | 394 | ||
323 | static int try_to_find_kprobe_trace_events(struct perf_probe_event *pev, | 395 | static int try_to_find_probe_trace_events(struct perf_probe_event *pev, |
324 | struct kprobe_trace_event **tevs __unused, | 396 | struct probe_trace_event **tevs __unused, |
325 | int max_tevs __unused) | 397 | int max_tevs __unused) |
326 | { | 398 | { |
327 | if (perf_probe_event_need_dwarf(pev)) { | 399 | if (perf_probe_event_need_dwarf(pev)) { |
@@ -557,7 +629,7 @@ static int parse_perf_probe_point(char *arg, struct perf_probe_event *pev) | |||
557 | /* Parse perf-probe event argument */ | 629 | /* Parse perf-probe event argument */ |
558 | static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) | 630 | static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) |
559 | { | 631 | { |
560 | char *tmp; | 632 | char *tmp, *goodname; |
561 | struct perf_probe_arg_field **fieldp; | 633 | struct perf_probe_arg_field **fieldp; |
562 | 634 | ||
563 | pr_debug("parsing arg: %s into ", str); | 635 | pr_debug("parsing arg: %s into ", str); |
@@ -580,7 +652,7 @@ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) | |||
580 | pr_debug("type:%s ", arg->type); | 652 | pr_debug("type:%s ", arg->type); |
581 | } | 653 | } |
582 | 654 | ||
583 | tmp = strpbrk(str, "-."); | 655 | tmp = strpbrk(str, "-.["); |
584 | if (!is_c_varname(str) || !tmp) { | 656 | if (!is_c_varname(str) || !tmp) { |
585 | /* A variable, register, symbol or special value */ | 657 | /* A variable, register, symbol or special value */ |
586 | arg->var = strdup(str); | 658 | arg->var = strdup(str); |
@@ -590,10 +662,11 @@ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) | |||
590 | return 0; | 662 | return 0; |
591 | } | 663 | } |
592 | 664 | ||
593 | /* Structure fields */ | 665 | /* Structure fields or array element */ |
594 | arg->var = strndup(str, tmp - str); | 666 | arg->var = strndup(str, tmp - str); |
595 | if (arg->var == NULL) | 667 | if (arg->var == NULL) |
596 | return -ENOMEM; | 668 | return -ENOMEM; |
669 | goodname = arg->var; | ||
597 | pr_debug("%s, ", arg->var); | 670 | pr_debug("%s, ", arg->var); |
598 | fieldp = &arg->field; | 671 | fieldp = &arg->field; |
599 | 672 | ||
@@ -601,22 +674,38 @@ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) | |||
601 | *fieldp = zalloc(sizeof(struct perf_probe_arg_field)); | 674 | *fieldp = zalloc(sizeof(struct perf_probe_arg_field)); |
602 | if (*fieldp == NULL) | 675 | if (*fieldp == NULL) |
603 | return -ENOMEM; | 676 | return -ENOMEM; |
604 | if (*tmp == '.') { | 677 | if (*tmp == '[') { /* Array */ |
605 | str = tmp + 1; | 678 | str = tmp; |
606 | (*fieldp)->ref = false; | 679 | (*fieldp)->index = strtol(str + 1, &tmp, 0); |
607 | } else if (tmp[1] == '>') { | ||
608 | str = tmp + 2; | ||
609 | (*fieldp)->ref = true; | 680 | (*fieldp)->ref = true; |
610 | } else { | 681 | if (*tmp != ']' || tmp == str + 1) { |
611 | semantic_error("Argument parse error: %s\n", str); | 682 | semantic_error("Array index must be a" |
612 | return -EINVAL; | 683 | " number.\n"); |
684 | return -EINVAL; | ||
685 | } | ||
686 | tmp++; | ||
687 | if (*tmp == '\0') | ||
688 | tmp = NULL; | ||
689 | } else { /* Structure */ | ||
690 | if (*tmp == '.') { | ||
691 | str = tmp + 1; | ||
692 | (*fieldp)->ref = false; | ||
693 | } else if (tmp[1] == '>') { | ||
694 | str = tmp + 2; | ||
695 | (*fieldp)->ref = true; | ||
696 | } else { | ||
697 | semantic_error("Argument parse error: %s\n", | ||
698 | str); | ||
699 | return -EINVAL; | ||
700 | } | ||
701 | tmp = strpbrk(str, "-.["); | ||
613 | } | 702 | } |
614 | |||
615 | tmp = strpbrk(str, "-."); | ||
616 | if (tmp) { | 703 | if (tmp) { |
617 | (*fieldp)->name = strndup(str, tmp - str); | 704 | (*fieldp)->name = strndup(str, tmp - str); |
618 | if ((*fieldp)->name == NULL) | 705 | if ((*fieldp)->name == NULL) |
619 | return -ENOMEM; | 706 | return -ENOMEM; |
707 | if (*str != '[') | ||
708 | goodname = (*fieldp)->name; | ||
620 | pr_debug("%s(%d), ", (*fieldp)->name, (*fieldp)->ref); | 709 | pr_debug("%s(%d), ", (*fieldp)->name, (*fieldp)->ref); |
621 | fieldp = &(*fieldp)->next; | 710 | fieldp = &(*fieldp)->next; |
622 | } | 711 | } |
@@ -624,11 +713,13 @@ static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg) | |||
624 | (*fieldp)->name = strdup(str); | 713 | (*fieldp)->name = strdup(str); |
625 | if ((*fieldp)->name == NULL) | 714 | if ((*fieldp)->name == NULL) |
626 | return -ENOMEM; | 715 | return -ENOMEM; |
716 | if (*str != '[') | ||
717 | goodname = (*fieldp)->name; | ||
627 | pr_debug("%s(%d)\n", (*fieldp)->name, (*fieldp)->ref); | 718 | pr_debug("%s(%d)\n", (*fieldp)->name, (*fieldp)->ref); |
628 | 719 | ||
629 | /* If no name is specified, set the last field name */ | 720 | /* If no name is specified, set the last field name (not array index)*/ |
630 | if (!arg->name) { | 721 | if (!arg->name) { |
631 | arg->name = strdup((*fieldp)->name); | 722 | arg->name = strdup(goodname); |
632 | if (arg->name == NULL) | 723 | if (arg->name == NULL) |
633 | return -ENOMEM; | 724 | return -ENOMEM; |
634 | } | 725 | } |
@@ -693,16 +784,17 @@ bool perf_probe_event_need_dwarf(struct perf_probe_event *pev) | |||
693 | return false; | 784 | return false; |
694 | } | 785 | } |
695 | 786 | ||
696 | /* Parse kprobe_events event into struct probe_point */ | 787 | /* Parse probe_events event into struct probe_point */ |
697 | int parse_kprobe_trace_command(const char *cmd, struct kprobe_trace_event *tev) | 788 | static int parse_probe_trace_command(const char *cmd, |
789 | struct probe_trace_event *tev) | ||
698 | { | 790 | { |
699 | struct kprobe_trace_point *tp = &tev->point; | 791 | struct probe_trace_point *tp = &tev->point; |
700 | char pr; | 792 | char pr; |
701 | char *p; | 793 | char *p; |
702 | int ret, i, argc; | 794 | int ret, i, argc; |
703 | char **argv; | 795 | char **argv; |
704 | 796 | ||
705 | pr_debug("Parsing kprobe_events: %s\n", cmd); | 797 | pr_debug("Parsing probe_events: %s\n", cmd); |
706 | argv = argv_split(cmd, &argc); | 798 | argv = argv_split(cmd, &argc); |
707 | if (!argv) { | 799 | if (!argv) { |
708 | pr_debug("Failed to split arguments.\n"); | 800 | pr_debug("Failed to split arguments.\n"); |
@@ -734,7 +826,7 @@ int parse_kprobe_trace_command(const char *cmd, struct kprobe_trace_event *tev) | |||
734 | tp->offset = 0; | 826 | tp->offset = 0; |
735 | 827 | ||
736 | tev->nargs = argc - 2; | 828 | tev->nargs = argc - 2; |
737 | tev->args = zalloc(sizeof(struct kprobe_trace_arg) * tev->nargs); | 829 | tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs); |
738 | if (tev->args == NULL) { | 830 | if (tev->args == NULL) { |
739 | ret = -ENOMEM; | 831 | ret = -ENOMEM; |
740 | goto out; | 832 | goto out; |
@@ -776,8 +868,11 @@ int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf, size_t len) | |||
776 | len -= ret; | 868 | len -= ret; |
777 | 869 | ||
778 | while (field) { | 870 | while (field) { |
779 | ret = e_snprintf(tmp, len, "%s%s", field->ref ? "->" : ".", | 871 | if (field->name[0] == '[') |
780 | field->name); | 872 | ret = e_snprintf(tmp, len, "%s", field->name); |
873 | else | ||
874 | ret = e_snprintf(tmp, len, "%s%s", | ||
875 | field->ref ? "->" : ".", field->name); | ||
781 | if (ret <= 0) | 876 | if (ret <= 0) |
782 | goto error; | 877 | goto error; |
783 | tmp += ret; | 878 | tmp += ret; |
@@ -877,13 +972,13 @@ char *synthesize_perf_probe_command(struct perf_probe_event *pev) | |||
877 | } | 972 | } |
878 | #endif | 973 | #endif |
879 | 974 | ||
880 | static int __synthesize_kprobe_trace_arg_ref(struct kprobe_trace_arg_ref *ref, | 975 | static int __synthesize_probe_trace_arg_ref(struct probe_trace_arg_ref *ref, |
881 | char **buf, size_t *buflen, | 976 | char **buf, size_t *buflen, |
882 | int depth) | 977 | int depth) |
883 | { | 978 | { |
884 | int ret; | 979 | int ret; |
885 | if (ref->next) { | 980 | if (ref->next) { |
886 | depth = __synthesize_kprobe_trace_arg_ref(ref->next, buf, | 981 | depth = __synthesize_probe_trace_arg_ref(ref->next, buf, |
887 | buflen, depth + 1); | 982 | buflen, depth + 1); |
888 | if (depth < 0) | 983 | if (depth < 0) |
889 | goto out; | 984 | goto out; |
@@ -901,9 +996,10 @@ out: | |||
901 | 996 | ||
902 | } | 997 | } |
903 | 998 | ||
904 | static int synthesize_kprobe_trace_arg(struct kprobe_trace_arg *arg, | 999 | static int synthesize_probe_trace_arg(struct probe_trace_arg *arg, |
905 | char *buf, size_t buflen) | 1000 | char *buf, size_t buflen) |
906 | { | 1001 | { |
1002 | struct probe_trace_arg_ref *ref = arg->ref; | ||
907 | int ret, depth = 0; | 1003 | int ret, depth = 0; |
908 | char *tmp = buf; | 1004 | char *tmp = buf; |
909 | 1005 | ||
@@ -917,16 +1013,24 @@ static int synthesize_kprobe_trace_arg(struct kprobe_trace_arg *arg, | |||
917 | buf += ret; | 1013 | buf += ret; |
918 | buflen -= ret; | 1014 | buflen -= ret; |
919 | 1015 | ||
1016 | /* Special case: @XXX */ | ||
1017 | if (arg->value[0] == '@' && arg->ref) | ||
1018 | ref = ref->next; | ||
1019 | |||
920 | /* Dereferencing arguments */ | 1020 | /* Dereferencing arguments */ |
921 | if (arg->ref) { | 1021 | if (ref) { |
922 | depth = __synthesize_kprobe_trace_arg_ref(arg->ref, &buf, | 1022 | depth = __synthesize_probe_trace_arg_ref(ref, &buf, |
923 | &buflen, 1); | 1023 | &buflen, 1); |
924 | if (depth < 0) | 1024 | if (depth < 0) |
925 | return depth; | 1025 | return depth; |
926 | } | 1026 | } |
927 | 1027 | ||
928 | /* Print argument value */ | 1028 | /* Print argument value */ |
929 | ret = e_snprintf(buf, buflen, "%s", arg->value); | 1029 | if (arg->value[0] == '@' && arg->ref) |
1030 | ret = e_snprintf(buf, buflen, "%s%+ld", arg->value, | ||
1031 | arg->ref->offset); | ||
1032 | else | ||
1033 | ret = e_snprintf(buf, buflen, "%s", arg->value); | ||
930 | if (ret < 0) | 1034 | if (ret < 0) |
931 | return ret; | 1035 | return ret; |
932 | buf += ret; | 1036 | buf += ret; |
@@ -951,9 +1055,9 @@ static int synthesize_kprobe_trace_arg(struct kprobe_trace_arg *arg, | |||
951 | return buf - tmp; | 1055 | return buf - tmp; |
952 | } | 1056 | } |
953 | 1057 | ||
954 | char *synthesize_kprobe_trace_command(struct kprobe_trace_event *tev) | 1058 | char *synthesize_probe_trace_command(struct probe_trace_event *tev) |
955 | { | 1059 | { |
956 | struct kprobe_trace_point *tp = &tev->point; | 1060 | struct probe_trace_point *tp = &tev->point; |
957 | char *buf; | 1061 | char *buf; |
958 | int i, len, ret; | 1062 | int i, len, ret; |
959 | 1063 | ||
@@ -969,7 +1073,7 @@ char *synthesize_kprobe_trace_command(struct kprobe_trace_event *tev) | |||
969 | goto error; | 1073 | goto error; |
970 | 1074 | ||
971 | for (i = 0; i < tev->nargs; i++) { | 1075 | for (i = 0; i < tev->nargs; i++) { |
972 | ret = synthesize_kprobe_trace_arg(&tev->args[i], buf + len, | 1076 | ret = synthesize_probe_trace_arg(&tev->args[i], buf + len, |
973 | MAX_CMDLEN - len); | 1077 | MAX_CMDLEN - len); |
974 | if (ret <= 0) | 1078 | if (ret <= 0) |
975 | goto error; | 1079 | goto error; |
@@ -982,7 +1086,7 @@ error: | |||
982 | return NULL; | 1086 | return NULL; |
983 | } | 1087 | } |
984 | 1088 | ||
985 | int convert_to_perf_probe_event(struct kprobe_trace_event *tev, | 1089 | static int convert_to_perf_probe_event(struct probe_trace_event *tev, |
986 | struct perf_probe_event *pev) | 1090 | struct perf_probe_event *pev) |
987 | { | 1091 | { |
988 | char buf[64] = ""; | 1092 | char buf[64] = ""; |
@@ -995,7 +1099,7 @@ int convert_to_perf_probe_event(struct kprobe_trace_event *tev, | |||
995 | return -ENOMEM; | 1099 | return -ENOMEM; |
996 | 1100 | ||
997 | /* Convert trace_point to probe_point */ | 1101 | /* Convert trace_point to probe_point */ |
998 | ret = convert_to_perf_probe_point(&tev->point, &pev->point); | 1102 | ret = kprobe_convert_to_perf_probe(&tev->point, &pev->point); |
999 | if (ret < 0) | 1103 | if (ret < 0) |
1000 | return ret; | 1104 | return ret; |
1001 | 1105 | ||
@@ -1008,7 +1112,7 @@ int convert_to_perf_probe_event(struct kprobe_trace_event *tev, | |||
1008 | if (tev->args[i].name) | 1112 | if (tev->args[i].name) |
1009 | pev->args[i].name = strdup(tev->args[i].name); | 1113 | pev->args[i].name = strdup(tev->args[i].name); |
1010 | else { | 1114 | else { |
1011 | ret = synthesize_kprobe_trace_arg(&tev->args[i], | 1115 | ret = synthesize_probe_trace_arg(&tev->args[i], |
1012 | buf, 64); | 1116 | buf, 64); |
1013 | pev->args[i].name = strdup(buf); | 1117 | pev->args[i].name = strdup(buf); |
1014 | } | 1118 | } |
@@ -1059,9 +1163,9 @@ void clear_perf_probe_event(struct perf_probe_event *pev) | |||
1059 | memset(pev, 0, sizeof(*pev)); | 1163 | memset(pev, 0, sizeof(*pev)); |
1060 | } | 1164 | } |
1061 | 1165 | ||
1062 | void clear_kprobe_trace_event(struct kprobe_trace_event *tev) | 1166 | static void clear_probe_trace_event(struct probe_trace_event *tev) |
1063 | { | 1167 | { |
1064 | struct kprobe_trace_arg_ref *ref, *next; | 1168 | struct probe_trace_arg_ref *ref, *next; |
1065 | int i; | 1169 | int i; |
1066 | 1170 | ||
1067 | if (tev->event) | 1171 | if (tev->event) |
@@ -1122,7 +1226,7 @@ static int open_kprobe_events(bool readwrite) | |||
1122 | } | 1226 | } |
1123 | 1227 | ||
1124 | /* Get raw string list of current kprobe_events */ | 1228 | /* Get raw string list of current kprobe_events */ |
1125 | static struct strlist *get_kprobe_trace_command_rawlist(int fd) | 1229 | static struct strlist *get_probe_trace_command_rawlist(int fd) |
1126 | { | 1230 | { |
1127 | int ret, idx; | 1231 | int ret, idx; |
1128 | FILE *fp; | 1232 | FILE *fp; |
@@ -1190,7 +1294,7 @@ static int show_perf_probe_event(struct perf_probe_event *pev) | |||
1190 | int show_perf_probe_events(void) | 1294 | int show_perf_probe_events(void) |
1191 | { | 1295 | { |
1192 | int fd, ret; | 1296 | int fd, ret; |
1193 | struct kprobe_trace_event tev; | 1297 | struct probe_trace_event tev; |
1194 | struct perf_probe_event pev; | 1298 | struct perf_probe_event pev; |
1195 | struct strlist *rawlist; | 1299 | struct strlist *rawlist; |
1196 | struct str_node *ent; | 1300 | struct str_node *ent; |
@@ -1207,20 +1311,20 @@ int show_perf_probe_events(void) | |||
1207 | if (fd < 0) | 1311 | if (fd < 0) |
1208 | return fd; | 1312 | return fd; |
1209 | 1313 | ||
1210 | rawlist = get_kprobe_trace_command_rawlist(fd); | 1314 | rawlist = get_probe_trace_command_rawlist(fd); |
1211 | close(fd); | 1315 | close(fd); |
1212 | if (!rawlist) | 1316 | if (!rawlist) |
1213 | return -ENOENT; | 1317 | return -ENOENT; |
1214 | 1318 | ||
1215 | strlist__for_each(ent, rawlist) { | 1319 | strlist__for_each(ent, rawlist) { |
1216 | ret = parse_kprobe_trace_command(ent->s, &tev); | 1320 | ret = parse_probe_trace_command(ent->s, &tev); |
1217 | if (ret >= 0) { | 1321 | if (ret >= 0) { |
1218 | ret = convert_to_perf_probe_event(&tev, &pev); | 1322 | ret = convert_to_perf_probe_event(&tev, &pev); |
1219 | if (ret >= 0) | 1323 | if (ret >= 0) |
1220 | ret = show_perf_probe_event(&pev); | 1324 | ret = show_perf_probe_event(&pev); |
1221 | } | 1325 | } |
1222 | clear_perf_probe_event(&pev); | 1326 | clear_perf_probe_event(&pev); |
1223 | clear_kprobe_trace_event(&tev); | 1327 | clear_probe_trace_event(&tev); |
1224 | if (ret < 0) | 1328 | if (ret < 0) |
1225 | break; | 1329 | break; |
1226 | } | 1330 | } |
@@ -1230,20 +1334,19 @@ int show_perf_probe_events(void) | |||
1230 | } | 1334 | } |
1231 | 1335 | ||
1232 | /* Get current perf-probe event names */ | 1336 | /* Get current perf-probe event names */ |
1233 | static struct strlist *get_kprobe_trace_event_names(int fd, bool include_group) | 1337 | static struct strlist *get_probe_trace_event_names(int fd, bool include_group) |
1234 | { | 1338 | { |
1235 | char buf[128]; | 1339 | char buf[128]; |
1236 | struct strlist *sl, *rawlist; | 1340 | struct strlist *sl, *rawlist; |
1237 | struct str_node *ent; | 1341 | struct str_node *ent; |
1238 | struct kprobe_trace_event tev; | 1342 | struct probe_trace_event tev; |
1239 | int ret = 0; | 1343 | int ret = 0; |
1240 | 1344 | ||
1241 | memset(&tev, 0, sizeof(tev)); | 1345 | memset(&tev, 0, sizeof(tev)); |
1242 | 1346 | rawlist = get_probe_trace_command_rawlist(fd); | |
1243 | rawlist = get_kprobe_trace_command_rawlist(fd); | ||
1244 | sl = strlist__new(true, NULL); | 1347 | sl = strlist__new(true, NULL); |
1245 | strlist__for_each(ent, rawlist) { | 1348 | strlist__for_each(ent, rawlist) { |
1246 | ret = parse_kprobe_trace_command(ent->s, &tev); | 1349 | ret = parse_probe_trace_command(ent->s, &tev); |
1247 | if (ret < 0) | 1350 | if (ret < 0) |
1248 | break; | 1351 | break; |
1249 | if (include_group) { | 1352 | if (include_group) { |
@@ -1253,7 +1356,7 @@ static struct strlist *get_kprobe_trace_event_names(int fd, bool include_group) | |||
1253 | ret = strlist__add(sl, buf); | 1356 | ret = strlist__add(sl, buf); |
1254 | } else | 1357 | } else |
1255 | ret = strlist__add(sl, tev.event); | 1358 | ret = strlist__add(sl, tev.event); |
1256 | clear_kprobe_trace_event(&tev); | 1359 | clear_probe_trace_event(&tev); |
1257 | if (ret < 0) | 1360 | if (ret < 0) |
1258 | break; | 1361 | break; |
1259 | } | 1362 | } |
@@ -1266,13 +1369,13 @@ static struct strlist *get_kprobe_trace_event_names(int fd, bool include_group) | |||
1266 | return sl; | 1369 | return sl; |
1267 | } | 1370 | } |
1268 | 1371 | ||
1269 | static int write_kprobe_trace_event(int fd, struct kprobe_trace_event *tev) | 1372 | static int write_probe_trace_event(int fd, struct probe_trace_event *tev) |
1270 | { | 1373 | { |
1271 | int ret = 0; | 1374 | int ret = 0; |
1272 | char *buf = synthesize_kprobe_trace_command(tev); | 1375 | char *buf = synthesize_probe_trace_command(tev); |
1273 | 1376 | ||
1274 | if (!buf) { | 1377 | if (!buf) { |
1275 | pr_debug("Failed to synthesize kprobe trace event.\n"); | 1378 | pr_debug("Failed to synthesize probe trace event.\n"); |
1276 | return -EINVAL; | 1379 | return -EINVAL; |
1277 | } | 1380 | } |
1278 | 1381 | ||
@@ -1325,12 +1428,12 @@ static int get_new_event_name(char *buf, size_t len, const char *base, | |||
1325 | return ret; | 1428 | return ret; |
1326 | } | 1429 | } |
1327 | 1430 | ||
1328 | static int __add_kprobe_trace_events(struct perf_probe_event *pev, | 1431 | static int __add_probe_trace_events(struct perf_probe_event *pev, |
1329 | struct kprobe_trace_event *tevs, | 1432 | struct probe_trace_event *tevs, |
1330 | int ntevs, bool allow_suffix) | 1433 | int ntevs, bool allow_suffix) |
1331 | { | 1434 | { |
1332 | int i, fd, ret; | 1435 | int i, fd, ret; |
1333 | struct kprobe_trace_event *tev = NULL; | 1436 | struct probe_trace_event *tev = NULL; |
1334 | char buf[64]; | 1437 | char buf[64]; |
1335 | const char *event, *group; | 1438 | const char *event, *group; |
1336 | struct strlist *namelist; | 1439 | struct strlist *namelist; |
@@ -1339,7 +1442,7 @@ static int __add_kprobe_trace_events(struct perf_probe_event *pev, | |||
1339 | if (fd < 0) | 1442 | if (fd < 0) |
1340 | return fd; | 1443 | return fd; |
1341 | /* Get current event names */ | 1444 | /* Get current event names */ |
1342 | namelist = get_kprobe_trace_event_names(fd, false); | 1445 | namelist = get_probe_trace_event_names(fd, false); |
1343 | if (!namelist) { | 1446 | if (!namelist) { |
1344 | pr_debug("Failed to get current event list.\n"); | 1447 | pr_debug("Failed to get current event list.\n"); |
1345 | return -EIO; | 1448 | return -EIO; |
@@ -1374,7 +1477,7 @@ static int __add_kprobe_trace_events(struct perf_probe_event *pev, | |||
1374 | ret = -ENOMEM; | 1477 | ret = -ENOMEM; |
1375 | break; | 1478 | break; |
1376 | } | 1479 | } |
1377 | ret = write_kprobe_trace_event(fd, tev); | 1480 | ret = write_probe_trace_event(fd, tev); |
1378 | if (ret < 0) | 1481 | if (ret < 0) |
1379 | break; | 1482 | break; |
1380 | /* Add added event name to namelist */ | 1483 | /* Add added event name to namelist */ |
@@ -1411,21 +1514,21 @@ static int __add_kprobe_trace_events(struct perf_probe_event *pev, | |||
1411 | return ret; | 1514 | return ret; |
1412 | } | 1515 | } |
1413 | 1516 | ||
1414 | static int convert_to_kprobe_trace_events(struct perf_probe_event *pev, | 1517 | static int convert_to_probe_trace_events(struct perf_probe_event *pev, |
1415 | struct kprobe_trace_event **tevs, | 1518 | struct probe_trace_event **tevs, |
1416 | int max_tevs) | 1519 | int max_tevs) |
1417 | { | 1520 | { |
1418 | struct symbol *sym; | 1521 | struct symbol *sym; |
1419 | int ret = 0, i; | 1522 | int ret = 0, i; |
1420 | struct kprobe_trace_event *tev; | 1523 | struct probe_trace_event *tev; |
1421 | 1524 | ||
1422 | /* Convert perf_probe_event with debuginfo */ | 1525 | /* Convert perf_probe_event with debuginfo */ |
1423 | ret = try_to_find_kprobe_trace_events(pev, tevs, max_tevs); | 1526 | ret = try_to_find_probe_trace_events(pev, tevs, max_tevs); |
1424 | if (ret != 0) | 1527 | if (ret != 0) |
1425 | return ret; | 1528 | return ret; |
1426 | 1529 | ||
1427 | /* Allocate trace event buffer */ | 1530 | /* Allocate trace event buffer */ |
1428 | tev = *tevs = zalloc(sizeof(struct kprobe_trace_event)); | 1531 | tev = *tevs = zalloc(sizeof(struct probe_trace_event)); |
1429 | if (tev == NULL) | 1532 | if (tev == NULL) |
1430 | return -ENOMEM; | 1533 | return -ENOMEM; |
1431 | 1534 | ||
@@ -1438,7 +1541,7 @@ static int convert_to_kprobe_trace_events(struct perf_probe_event *pev, | |||
1438 | tev->point.offset = pev->point.offset; | 1541 | tev->point.offset = pev->point.offset; |
1439 | tev->nargs = pev->nargs; | 1542 | tev->nargs = pev->nargs; |
1440 | if (tev->nargs) { | 1543 | if (tev->nargs) { |
1441 | tev->args = zalloc(sizeof(struct kprobe_trace_arg) | 1544 | tev->args = zalloc(sizeof(struct probe_trace_arg) |
1442 | * tev->nargs); | 1545 | * tev->nargs); |
1443 | if (tev->args == NULL) { | 1546 | if (tev->args == NULL) { |
1444 | ret = -ENOMEM; | 1547 | ret = -ENOMEM; |
@@ -1479,7 +1582,7 @@ static int convert_to_kprobe_trace_events(struct perf_probe_event *pev, | |||
1479 | 1582 | ||
1480 | return 1; | 1583 | return 1; |
1481 | error: | 1584 | error: |
1482 | clear_kprobe_trace_event(tev); | 1585 | clear_probe_trace_event(tev); |
1483 | free(tev); | 1586 | free(tev); |
1484 | *tevs = NULL; | 1587 | *tevs = NULL; |
1485 | return ret; | 1588 | return ret; |
@@ -1487,7 +1590,7 @@ error: | |||
1487 | 1590 | ||
1488 | struct __event_package { | 1591 | struct __event_package { |
1489 | struct perf_probe_event *pev; | 1592 | struct perf_probe_event *pev; |
1490 | struct kprobe_trace_event *tevs; | 1593 | struct probe_trace_event *tevs; |
1491 | int ntevs; | 1594 | int ntevs; |
1492 | }; | 1595 | }; |
1493 | 1596 | ||
@@ -1503,14 +1606,16 @@ int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, | |||
1503 | 1606 | ||
1504 | /* Init vmlinux path */ | 1607 | /* Init vmlinux path */ |
1505 | ret = init_vmlinux(); | 1608 | ret = init_vmlinux(); |
1506 | if (ret < 0) | 1609 | if (ret < 0) { |
1610 | free(pkgs); | ||
1507 | return ret; | 1611 | return ret; |
1612 | } | ||
1508 | 1613 | ||
1509 | /* Loop 1: convert all events */ | 1614 | /* Loop 1: convert all events */ |
1510 | for (i = 0; i < npevs; i++) { | 1615 | for (i = 0; i < npevs; i++) { |
1511 | pkgs[i].pev = &pevs[i]; | 1616 | pkgs[i].pev = &pevs[i]; |
1512 | /* Convert with or without debuginfo */ | 1617 | /* Convert with or without debuginfo */ |
1513 | ret = convert_to_kprobe_trace_events(pkgs[i].pev, | 1618 | ret = convert_to_probe_trace_events(pkgs[i].pev, |
1514 | &pkgs[i].tevs, max_tevs); | 1619 | &pkgs[i].tevs, max_tevs); |
1515 | if (ret < 0) | 1620 | if (ret < 0) |
1516 | goto end; | 1621 | goto end; |
@@ -1519,24 +1624,27 @@ int add_perf_probe_events(struct perf_probe_event *pevs, int npevs, | |||
1519 | 1624 | ||
1520 | /* Loop 2: add all events */ | 1625 | /* Loop 2: add all events */ |
1521 | for (i = 0; i < npevs && ret >= 0; i++) | 1626 | for (i = 0; i < npevs && ret >= 0; i++) |
1522 | ret = __add_kprobe_trace_events(pkgs[i].pev, pkgs[i].tevs, | 1627 | ret = __add_probe_trace_events(pkgs[i].pev, pkgs[i].tevs, |
1523 | pkgs[i].ntevs, force_add); | 1628 | pkgs[i].ntevs, force_add); |
1524 | end: | 1629 | end: |
1525 | /* Loop 3: cleanup trace events */ | 1630 | /* Loop 3: cleanup and free trace events */ |
1526 | for (i = 0; i < npevs; i++) | 1631 | for (i = 0; i < npevs; i++) { |
1527 | for (j = 0; j < pkgs[i].ntevs; j++) | 1632 | for (j = 0; j < pkgs[i].ntevs; j++) |
1528 | clear_kprobe_trace_event(&pkgs[i].tevs[j]); | 1633 | clear_probe_trace_event(&pkgs[i].tevs[j]); |
1634 | free(pkgs[i].tevs); | ||
1635 | } | ||
1636 | free(pkgs); | ||
1529 | 1637 | ||
1530 | return ret; | 1638 | return ret; |
1531 | } | 1639 | } |
1532 | 1640 | ||
1533 | static int __del_trace_kprobe_event(int fd, struct str_node *ent) | 1641 | static int __del_trace_probe_event(int fd, struct str_node *ent) |
1534 | { | 1642 | { |
1535 | char *p; | 1643 | char *p; |
1536 | char buf[128]; | 1644 | char buf[128]; |
1537 | int ret; | 1645 | int ret; |
1538 | 1646 | ||
1539 | /* Convert from perf-probe event to trace-kprobe event */ | 1647 | /* Convert from perf-probe event to trace-probe event */ |
1540 | ret = e_snprintf(buf, 128, "-:%s", ent->s); | 1648 | ret = e_snprintf(buf, 128, "-:%s", ent->s); |
1541 | if (ret < 0) | 1649 | if (ret < 0) |
1542 | goto error; | 1650 | goto error; |
@@ -1562,7 +1670,7 @@ error: | |||
1562 | return ret; | 1670 | return ret; |
1563 | } | 1671 | } |
1564 | 1672 | ||
1565 | static int del_trace_kprobe_event(int fd, const char *group, | 1673 | static int del_trace_probe_event(int fd, const char *group, |
1566 | const char *event, struct strlist *namelist) | 1674 | const char *event, struct strlist *namelist) |
1567 | { | 1675 | { |
1568 | char buf[128]; | 1676 | char buf[128]; |
@@ -1579,7 +1687,7 @@ static int del_trace_kprobe_event(int fd, const char *group, | |||
1579 | strlist__for_each_safe(ent, n, namelist) | 1687 | strlist__for_each_safe(ent, n, namelist) |
1580 | if (strglobmatch(ent->s, buf)) { | 1688 | if (strglobmatch(ent->s, buf)) { |
1581 | found++; | 1689 | found++; |
1582 | ret = __del_trace_kprobe_event(fd, ent); | 1690 | ret = __del_trace_probe_event(fd, ent); |
1583 | if (ret < 0) | 1691 | if (ret < 0) |
1584 | break; | 1692 | break; |
1585 | strlist__remove(namelist, ent); | 1693 | strlist__remove(namelist, ent); |
@@ -1588,7 +1696,7 @@ static int del_trace_kprobe_event(int fd, const char *group, | |||
1588 | ent = strlist__find(namelist, buf); | 1696 | ent = strlist__find(namelist, buf); |
1589 | if (ent) { | 1697 | if (ent) { |
1590 | found++; | 1698 | found++; |
1591 | ret = __del_trace_kprobe_event(fd, ent); | 1699 | ret = __del_trace_probe_event(fd, ent); |
1592 | if (ret >= 0) | 1700 | if (ret >= 0) |
1593 | strlist__remove(namelist, ent); | 1701 | strlist__remove(namelist, ent); |
1594 | } | 1702 | } |
@@ -1612,7 +1720,7 @@ int del_perf_probe_events(struct strlist *dellist) | |||
1612 | return fd; | 1720 | return fd; |
1613 | 1721 | ||
1614 | /* Get current event names */ | 1722 | /* Get current event names */ |
1615 | namelist = get_kprobe_trace_event_names(fd, true); | 1723 | namelist = get_probe_trace_event_names(fd, true); |
1616 | if (namelist == NULL) | 1724 | if (namelist == NULL) |
1617 | return -EINVAL; | 1725 | return -EINVAL; |
1618 | 1726 | ||
@@ -1633,7 +1741,7 @@ int del_perf_probe_events(struct strlist *dellist) | |||
1633 | event = str; | 1741 | event = str; |
1634 | } | 1742 | } |
1635 | pr_debug("Group: %s, Event: %s\n", group, event); | 1743 | pr_debug("Group: %s, Event: %s\n", group, event); |
1636 | ret = del_trace_kprobe_event(fd, group, event, namelist); | 1744 | ret = del_trace_probe_event(fd, group, event, namelist); |
1637 | free(str); | 1745 | free(str); |
1638 | if (ret < 0) | 1746 | if (ret < 0) |
1639 | break; | 1747 | break; |
diff --git a/tools/perf/util/probe-event.h b/tools/perf/util/probe-event.h index e9db1a214ca4..5af39243a25b 100644 --- a/tools/perf/util/probe-event.h +++ b/tools/perf/util/probe-event.h | |||
@@ -7,33 +7,33 @@ | |||
7 | extern bool probe_event_dry_run; | 7 | extern bool probe_event_dry_run; |
8 | 8 | ||
9 | /* kprobe-tracer tracing point */ | 9 | /* kprobe-tracer tracing point */ |
10 | struct kprobe_trace_point { | 10 | struct probe_trace_point { |
11 | char *symbol; /* Base symbol */ | 11 | char *symbol; /* Base symbol */ |
12 | unsigned long offset; /* Offset from symbol */ | 12 | unsigned long offset; /* Offset from symbol */ |
13 | bool retprobe; /* Return probe flag */ | 13 | bool retprobe; /* Return probe flag */ |
14 | }; | 14 | }; |
15 | 15 | ||
16 | /* kprobe-tracer tracing argument referencing offset */ | 16 | /* probe-tracer tracing argument referencing offset */ |
17 | struct kprobe_trace_arg_ref { | 17 | struct probe_trace_arg_ref { |
18 | struct kprobe_trace_arg_ref *next; /* Next reference */ | 18 | struct probe_trace_arg_ref *next; /* Next reference */ |
19 | long offset; /* Offset value */ | 19 | long offset; /* Offset value */ |
20 | }; | 20 | }; |
21 | 21 | ||
22 | /* kprobe-tracer tracing argument */ | 22 | /* kprobe-tracer tracing argument */ |
23 | struct kprobe_trace_arg { | 23 | struct probe_trace_arg { |
24 | char *name; /* Argument name */ | 24 | char *name; /* Argument name */ |
25 | char *value; /* Base value */ | 25 | char *value; /* Base value */ |
26 | char *type; /* Type name */ | 26 | char *type; /* Type name */ |
27 | struct kprobe_trace_arg_ref *ref; /* Referencing offset */ | 27 | struct probe_trace_arg_ref *ref; /* Referencing offset */ |
28 | }; | 28 | }; |
29 | 29 | ||
30 | /* kprobe-tracer tracing event (point + arg) */ | 30 | /* kprobe-tracer tracing event (point + arg) */ |
31 | struct kprobe_trace_event { | 31 | struct probe_trace_event { |
32 | char *event; /* Event name */ | 32 | char *event; /* Event name */ |
33 | char *group; /* Group name */ | 33 | char *group; /* Group name */ |
34 | struct kprobe_trace_point point; /* Trace point */ | 34 | struct probe_trace_point point; /* Trace point */ |
35 | int nargs; /* Number of args */ | 35 | int nargs; /* Number of args */ |
36 | struct kprobe_trace_arg *args; /* Arguments */ | 36 | struct probe_trace_arg *args; /* Arguments */ |
37 | }; | 37 | }; |
38 | 38 | ||
39 | /* Perf probe probing point */ | 39 | /* Perf probe probing point */ |
@@ -50,6 +50,7 @@ struct perf_probe_point { | |||
50 | struct perf_probe_arg_field { | 50 | struct perf_probe_arg_field { |
51 | struct perf_probe_arg_field *next; /* Next field */ | 51 | struct perf_probe_arg_field *next; /* Next field */ |
52 | char *name; /* Name of the field */ | 52 | char *name; /* Name of the field */ |
53 | long index; /* Array index number */ | ||
53 | bool ref; /* Referencing flag */ | 54 | bool ref; /* Referencing flag */ |
54 | }; | 55 | }; |
55 | 56 | ||
@@ -85,31 +86,25 @@ struct line_range { | |||
85 | int end; /* End line number */ | 86 | int end; /* End line number */ |
86 | int offset; /* Start line offset */ | 87 | int offset; /* Start line offset */ |
87 | char *path; /* Real path name */ | 88 | char *path; /* Real path name */ |
89 | char *comp_dir; /* Compile directory */ | ||
88 | struct list_head line_list; /* Visible lines */ | 90 | struct list_head line_list; /* Visible lines */ |
89 | }; | 91 | }; |
90 | 92 | ||
91 | /* Command string to events */ | 93 | /* Command string to events */ |
92 | extern int parse_perf_probe_command(const char *cmd, | 94 | extern int parse_perf_probe_command(const char *cmd, |
93 | struct perf_probe_event *pev); | 95 | struct perf_probe_event *pev); |
94 | extern int parse_kprobe_trace_command(const char *cmd, | ||
95 | struct kprobe_trace_event *tev); | ||
96 | 96 | ||
97 | /* Events to command string */ | 97 | /* Events to command string */ |
98 | extern char *synthesize_perf_probe_command(struct perf_probe_event *pev); | 98 | extern char *synthesize_perf_probe_command(struct perf_probe_event *pev); |
99 | extern char *synthesize_kprobe_trace_command(struct kprobe_trace_event *tev); | 99 | extern char *synthesize_probe_trace_command(struct probe_trace_event *tev); |
100 | extern int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf, | 100 | extern int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf, |
101 | size_t len); | 101 | size_t len); |
102 | 102 | ||
103 | /* Check the perf_probe_event needs debuginfo */ | 103 | /* Check the perf_probe_event needs debuginfo */ |
104 | extern bool perf_probe_event_need_dwarf(struct perf_probe_event *pev); | 104 | extern bool perf_probe_event_need_dwarf(struct perf_probe_event *pev); |
105 | 105 | ||
106 | /* Convert from kprobe_trace_event to perf_probe_event */ | ||
107 | extern int convert_to_perf_probe_event(struct kprobe_trace_event *tev, | ||
108 | struct perf_probe_event *pev); | ||
109 | |||
110 | /* Release event contents */ | 106 | /* Release event contents */ |
111 | extern void clear_perf_probe_event(struct perf_probe_event *pev); | 107 | extern void clear_perf_probe_event(struct perf_probe_event *pev); |
112 | extern void clear_kprobe_trace_event(struct kprobe_trace_event *tev); | ||
113 | 108 | ||
114 | /* Command string to line-range */ | 109 | /* Command string to line-range */ |
115 | extern int parse_line_range_desc(const char *cmd, struct line_range *lr); | 110 | extern int parse_line_range_desc(const char *cmd, struct line_range *lr); |
diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index d964cb199c67..525136684d4e 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c | |||
@@ -33,10 +33,10 @@ | |||
33 | #include <ctype.h> | 33 | #include <ctype.h> |
34 | #include <dwarf-regs.h> | 34 | #include <dwarf-regs.h> |
35 | 35 | ||
36 | #include "string.h" | ||
37 | #include "event.h" | 36 | #include "event.h" |
38 | #include "debug.h" | 37 | #include "debug.h" |
39 | #include "util.h" | 38 | #include "util.h" |
39 | #include "symbol.h" | ||
40 | #include "probe-finder.h" | 40 | #include "probe-finder.h" |
41 | 41 | ||
42 | /* Kprobe tracer basic type is up to u64 */ | 42 | /* Kprobe tracer basic type is up to u64 */ |
@@ -143,12 +143,21 @@ static const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname) | |||
143 | return src; | 143 | return src; |
144 | } | 144 | } |
145 | 145 | ||
146 | /* Get DW_AT_comp_dir (should be NULL with older gcc) */ | ||
147 | static const char *cu_get_comp_dir(Dwarf_Die *cu_die) | ||
148 | { | ||
149 | Dwarf_Attribute attr; | ||
150 | if (dwarf_attr(cu_die, DW_AT_comp_dir, &attr) == NULL) | ||
151 | return NULL; | ||
152 | return dwarf_formstring(&attr); | ||
153 | } | ||
154 | |||
146 | /* Compare diename and tname */ | 155 | /* Compare diename and tname */ |
147 | static bool die_compare_name(Dwarf_Die *dw_die, const char *tname) | 156 | static bool die_compare_name(Dwarf_Die *dw_die, const char *tname) |
148 | { | 157 | { |
149 | const char *name; | 158 | const char *name; |
150 | name = dwarf_diename(dw_die); | 159 | name = dwarf_diename(dw_die); |
151 | return name ? strcmp(tname, name) : -1; | 160 | return name ? (strcmp(tname, name) == 0) : false; |
152 | } | 161 | } |
153 | 162 | ||
154 | /* Get type die, but skip qualifiers and typedef */ | 163 | /* Get type die, but skip qualifiers and typedef */ |
@@ -319,7 +328,7 @@ static int __die_find_variable_cb(Dwarf_Die *die_mem, void *data) | |||
319 | tag = dwarf_tag(die_mem); | 328 | tag = dwarf_tag(die_mem); |
320 | if ((tag == DW_TAG_formal_parameter || | 329 | if ((tag == DW_TAG_formal_parameter || |
321 | tag == DW_TAG_variable) && | 330 | tag == DW_TAG_variable) && |
322 | (die_compare_name(die_mem, name) == 0)) | 331 | die_compare_name(die_mem, name)) |
323 | return DIE_FIND_CB_FOUND; | 332 | return DIE_FIND_CB_FOUND; |
324 | 333 | ||
325 | return DIE_FIND_CB_CONTINUE; | 334 | return DIE_FIND_CB_CONTINUE; |
@@ -338,7 +347,7 @@ static int __die_find_member_cb(Dwarf_Die *die_mem, void *data) | |||
338 | const char *name = data; | 347 | const char *name = data; |
339 | 348 | ||
340 | if ((dwarf_tag(die_mem) == DW_TAG_member) && | 349 | if ((dwarf_tag(die_mem) == DW_TAG_member) && |
341 | (die_compare_name(die_mem, name) == 0)) | 350 | die_compare_name(die_mem, name)) |
342 | return DIE_FIND_CB_FOUND; | 351 | return DIE_FIND_CB_FOUND; |
343 | 352 | ||
344 | return DIE_FIND_CB_SIBLING; | 353 | return DIE_FIND_CB_SIBLING; |
@@ -356,14 +365,50 @@ static Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name, | |||
356 | * Probe finder related functions | 365 | * Probe finder related functions |
357 | */ | 366 | */ |
358 | 367 | ||
368 | static struct probe_trace_arg_ref *alloc_trace_arg_ref(long offs) | ||
369 | { | ||
370 | struct probe_trace_arg_ref *ref; | ||
371 | ref = zalloc(sizeof(struct probe_trace_arg_ref)); | ||
372 | if (ref != NULL) | ||
373 | ref->offset = offs; | ||
374 | return ref; | ||
375 | } | ||
376 | |||
359 | /* Show a location */ | 377 | /* Show a location */ |
360 | static int convert_location(Dwarf_Op *op, struct probe_finder *pf) | 378 | static int convert_variable_location(Dwarf_Die *vr_die, struct probe_finder *pf) |
361 | { | 379 | { |
380 | Dwarf_Attribute attr; | ||
381 | Dwarf_Op *op; | ||
382 | size_t nops; | ||
362 | unsigned int regn; | 383 | unsigned int regn; |
363 | Dwarf_Word offs = 0; | 384 | Dwarf_Word offs = 0; |
364 | bool ref = false; | 385 | bool ref = false; |
365 | const char *regs; | 386 | const char *regs; |
366 | struct kprobe_trace_arg *tvar = pf->tvar; | 387 | struct probe_trace_arg *tvar = pf->tvar; |
388 | int ret; | ||
389 | |||
390 | /* TODO: handle more than 1 exprs */ | ||
391 | if (dwarf_attr(vr_die, DW_AT_location, &attr) == NULL || | ||
392 | dwarf_getlocation_addr(&attr, pf->addr, &op, &nops, 1) <= 0 || | ||
393 | nops == 0) { | ||
394 | /* TODO: Support const_value */ | ||
395 | pr_err("Failed to find the location of %s at this address.\n" | ||
396 | " Perhaps, it has been optimized out.\n", pf->pvar->var); | ||
397 | return -ENOENT; | ||
398 | } | ||
399 | |||
400 | if (op->atom == DW_OP_addr) { | ||
401 | /* Static variables on memory (not stack), make @varname */ | ||
402 | ret = strlen(dwarf_diename(vr_die)); | ||
403 | tvar->value = zalloc(ret + 2); | ||
404 | if (tvar->value == NULL) | ||
405 | return -ENOMEM; | ||
406 | snprintf(tvar->value, ret + 2, "@%s", dwarf_diename(vr_die)); | ||
407 | tvar->ref = alloc_trace_arg_ref((long)offs); | ||
408 | if (tvar->ref == NULL) | ||
409 | return -ENOMEM; | ||
410 | return 0; | ||
411 | } | ||
367 | 412 | ||
368 | /* If this is based on frame buffer, set the offset */ | 413 | /* If this is based on frame buffer, set the offset */ |
369 | if (op->atom == DW_OP_fbreg) { | 414 | if (op->atom == DW_OP_fbreg) { |
@@ -405,27 +450,72 @@ static int convert_location(Dwarf_Op *op, struct probe_finder *pf) | |||
405 | return -ENOMEM; | 450 | return -ENOMEM; |
406 | 451 | ||
407 | if (ref) { | 452 | if (ref) { |
408 | tvar->ref = zalloc(sizeof(struct kprobe_trace_arg_ref)); | 453 | tvar->ref = alloc_trace_arg_ref((long)offs); |
409 | if (tvar->ref == NULL) | 454 | if (tvar->ref == NULL) |
410 | return -ENOMEM; | 455 | return -ENOMEM; |
411 | tvar->ref->offset = (long)offs; | ||
412 | } | 456 | } |
413 | return 0; | 457 | return 0; |
414 | } | 458 | } |
415 | 459 | ||
416 | static int convert_variable_type(Dwarf_Die *vr_die, | 460 | static int convert_variable_type(Dwarf_Die *vr_die, |
417 | struct kprobe_trace_arg *targ) | 461 | struct probe_trace_arg *tvar, |
462 | const char *cast) | ||
418 | { | 463 | { |
464 | struct probe_trace_arg_ref **ref_ptr = &tvar->ref; | ||
419 | Dwarf_Die type; | 465 | Dwarf_Die type; |
420 | char buf[16]; | 466 | char buf[16]; |
421 | int ret; | 467 | int ret; |
422 | 468 | ||
469 | /* TODO: check all types */ | ||
470 | if (cast && strcmp(cast, "string") != 0) { | ||
471 | /* Non string type is OK */ | ||
472 | tvar->type = strdup(cast); | ||
473 | return (tvar->type == NULL) ? -ENOMEM : 0; | ||
474 | } | ||
475 | |||
423 | if (die_get_real_type(vr_die, &type) == NULL) { | 476 | if (die_get_real_type(vr_die, &type) == NULL) { |
424 | pr_warning("Failed to get a type information of %s.\n", | 477 | pr_warning("Failed to get a type information of %s.\n", |
425 | dwarf_diename(vr_die)); | 478 | dwarf_diename(vr_die)); |
426 | return -ENOENT; | 479 | return -ENOENT; |
427 | } | 480 | } |
428 | 481 | ||
482 | pr_debug("%s type is %s.\n", | ||
483 | dwarf_diename(vr_die), dwarf_diename(&type)); | ||
484 | |||
485 | if (cast && strcmp(cast, "string") == 0) { /* String type */ | ||
486 | ret = dwarf_tag(&type); | ||
487 | if (ret != DW_TAG_pointer_type && | ||
488 | ret != DW_TAG_array_type) { | ||
489 | pr_warning("Failed to cast into string: " | ||
490 | "%s(%s) is not a pointer nor array.", | ||
491 | dwarf_diename(vr_die), dwarf_diename(&type)); | ||
492 | return -EINVAL; | ||
493 | } | ||
494 | if (ret == DW_TAG_pointer_type) { | ||
495 | if (die_get_real_type(&type, &type) == NULL) { | ||
496 | pr_warning("Failed to get a type information."); | ||
497 | return -ENOENT; | ||
498 | } | ||
499 | while (*ref_ptr) | ||
500 | ref_ptr = &(*ref_ptr)->next; | ||
501 | /* Add new reference with offset +0 */ | ||
502 | *ref_ptr = zalloc(sizeof(struct probe_trace_arg_ref)); | ||
503 | if (*ref_ptr == NULL) { | ||
504 | pr_warning("Out of memory error\n"); | ||
505 | return -ENOMEM; | ||
506 | } | ||
507 | } | ||
508 | if (!die_compare_name(&type, "char") && | ||
509 | !die_compare_name(&type, "unsigned char")) { | ||
510 | pr_warning("Failed to cast into string: " | ||
511 | "%s is not (unsigned) char *.", | ||
512 | dwarf_diename(vr_die)); | ||
513 | return -EINVAL; | ||
514 | } | ||
515 | tvar->type = strdup(cast); | ||
516 | return (tvar->type == NULL) ? -ENOMEM : 0; | ||
517 | } | ||
518 | |||
429 | ret = die_get_byte_size(&type) * 8; | 519 | ret = die_get_byte_size(&type) * 8; |
430 | if (ret) { | 520 | if (ret) { |
431 | /* Check the bitwidth */ | 521 | /* Check the bitwidth */ |
@@ -445,8 +535,8 @@ static int convert_variable_type(Dwarf_Die *vr_die, | |||
445 | strerror(-ret)); | 535 | strerror(-ret)); |
446 | return ret; | 536 | return ret; |
447 | } | 537 | } |
448 | targ->type = strdup(buf); | 538 | tvar->type = strdup(buf); |
449 | if (targ->type == NULL) | 539 | if (tvar->type == NULL) |
450 | return -ENOMEM; | 540 | return -ENOMEM; |
451 | } | 541 | } |
452 | return 0; | 542 | return 0; |
@@ -454,22 +544,50 @@ static int convert_variable_type(Dwarf_Die *vr_die, | |||
454 | 544 | ||
455 | static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, | 545 | static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, |
456 | struct perf_probe_arg_field *field, | 546 | struct perf_probe_arg_field *field, |
457 | struct kprobe_trace_arg_ref **ref_ptr, | 547 | struct probe_trace_arg_ref **ref_ptr, |
458 | Dwarf_Die *die_mem) | 548 | Dwarf_Die *die_mem) |
459 | { | 549 | { |
460 | struct kprobe_trace_arg_ref *ref = *ref_ptr; | 550 | struct probe_trace_arg_ref *ref = *ref_ptr; |
461 | Dwarf_Die type; | 551 | Dwarf_Die type; |
462 | Dwarf_Word offs; | 552 | Dwarf_Word offs; |
463 | int ret; | 553 | int ret, tag; |
464 | 554 | ||
465 | pr_debug("converting %s in %s\n", field->name, varname); | 555 | pr_debug("converting %s in %s\n", field->name, varname); |
466 | if (die_get_real_type(vr_die, &type) == NULL) { | 556 | if (die_get_real_type(vr_die, &type) == NULL) { |
467 | pr_warning("Failed to get the type of %s.\n", varname); | 557 | pr_warning("Failed to get the type of %s.\n", varname); |
468 | return -ENOENT; | 558 | return -ENOENT; |
469 | } | 559 | } |
470 | 560 | pr_debug2("Var real type: (%x)\n", (unsigned)dwarf_dieoffset(&type)); | |
471 | /* Check the pointer and dereference */ | 561 | tag = dwarf_tag(&type); |
472 | if (dwarf_tag(&type) == DW_TAG_pointer_type) { | 562 | |
563 | if (field->name[0] == '[' && | ||
564 | (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)) { | ||
565 | if (field->next) | ||
566 | /* Save original type for next field */ | ||
567 | memcpy(die_mem, &type, sizeof(*die_mem)); | ||
568 | /* Get the type of this array */ | ||
569 | if (die_get_real_type(&type, &type) == NULL) { | ||
570 | pr_warning("Failed to get the type of %s.\n", varname); | ||
571 | return -ENOENT; | ||
572 | } | ||
573 | pr_debug2("Array real type: (%x)\n", | ||
574 | (unsigned)dwarf_dieoffset(&type)); | ||
575 | if (tag == DW_TAG_pointer_type) { | ||
576 | ref = zalloc(sizeof(struct probe_trace_arg_ref)); | ||
577 | if (ref == NULL) | ||
578 | return -ENOMEM; | ||
579 | if (*ref_ptr) | ||
580 | (*ref_ptr)->next = ref; | ||
581 | else | ||
582 | *ref_ptr = ref; | ||
583 | } | ||
584 | ref->offset += die_get_byte_size(&type) * field->index; | ||
585 | if (!field->next) | ||
586 | /* Save vr_die for converting types */ | ||
587 | memcpy(die_mem, vr_die, sizeof(*die_mem)); | ||
588 | goto next; | ||
589 | } else if (tag == DW_TAG_pointer_type) { | ||
590 | /* Check the pointer and dereference */ | ||
473 | if (!field->ref) { | 591 | if (!field->ref) { |
474 | pr_err("Semantic error: %s must be referred by '->'\n", | 592 | pr_err("Semantic error: %s must be referred by '->'\n", |
475 | field->name); | 593 | field->name); |
@@ -486,7 +604,7 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, | |||
486 | return -EINVAL; | 604 | return -EINVAL; |
487 | } | 605 | } |
488 | 606 | ||
489 | ref = zalloc(sizeof(struct kprobe_trace_arg_ref)); | 607 | ref = zalloc(sizeof(struct probe_trace_arg_ref)); |
490 | if (ref == NULL) | 608 | if (ref == NULL) |
491 | return -ENOMEM; | 609 | return -ENOMEM; |
492 | if (*ref_ptr) | 610 | if (*ref_ptr) |
@@ -495,10 +613,15 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, | |||
495 | *ref_ptr = ref; | 613 | *ref_ptr = ref; |
496 | } else { | 614 | } else { |
497 | /* Verify it is a data structure */ | 615 | /* Verify it is a data structure */ |
498 | if (dwarf_tag(&type) != DW_TAG_structure_type) { | 616 | if (tag != DW_TAG_structure_type) { |
499 | pr_warning("%s is not a data structure.\n", varname); | 617 | pr_warning("%s is not a data structure.\n", varname); |
500 | return -EINVAL; | 618 | return -EINVAL; |
501 | } | 619 | } |
620 | if (field->name[0] == '[') { | ||
621 | pr_err("Semantic error: %s is not a pointor nor array.", | ||
622 | varname); | ||
623 | return -EINVAL; | ||
624 | } | ||
502 | if (field->ref) { | 625 | if (field->ref) { |
503 | pr_err("Semantic error: %s must be referred by '.'\n", | 626 | pr_err("Semantic error: %s must be referred by '.'\n", |
504 | field->name); | 627 | field->name); |
@@ -525,6 +648,7 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, | |||
525 | } | 648 | } |
526 | ref->offset += (long)offs; | 649 | ref->offset += (long)offs; |
527 | 650 | ||
651 | next: | ||
528 | /* Converting next field */ | 652 | /* Converting next field */ |
529 | if (field->next) | 653 | if (field->next) |
530 | return convert_variable_fields(die_mem, field->name, | 654 | return convert_variable_fields(die_mem, field->name, |
@@ -536,51 +660,32 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, | |||
536 | /* Show a variables in kprobe event format */ | 660 | /* Show a variables in kprobe event format */ |
537 | static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf) | 661 | static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf) |
538 | { | 662 | { |
539 | Dwarf_Attribute attr; | ||
540 | Dwarf_Die die_mem; | 663 | Dwarf_Die die_mem; |
541 | Dwarf_Op *expr; | ||
542 | size_t nexpr; | ||
543 | int ret; | 664 | int ret; |
544 | 665 | ||
545 | if (dwarf_attr(vr_die, DW_AT_location, &attr) == NULL) | 666 | pr_debug("Converting variable %s into trace event.\n", |
546 | goto error; | 667 | dwarf_diename(vr_die)); |
547 | /* TODO: handle more than 1 exprs */ | ||
548 | ret = dwarf_getlocation_addr(&attr, pf->addr, &expr, &nexpr, 1); | ||
549 | if (ret <= 0 || nexpr == 0) | ||
550 | goto error; | ||
551 | 668 | ||
552 | ret = convert_location(expr, pf); | 669 | ret = convert_variable_location(vr_die, pf); |
553 | if (ret == 0 && pf->pvar->field) { | 670 | if (ret == 0 && pf->pvar->field) { |
554 | ret = convert_variable_fields(vr_die, pf->pvar->var, | 671 | ret = convert_variable_fields(vr_die, pf->pvar->var, |
555 | pf->pvar->field, &pf->tvar->ref, | 672 | pf->pvar->field, &pf->tvar->ref, |
556 | &die_mem); | 673 | &die_mem); |
557 | vr_die = &die_mem; | 674 | vr_die = &die_mem; |
558 | } | 675 | } |
559 | if (ret == 0) { | 676 | if (ret == 0) |
560 | if (pf->pvar->type) { | 677 | ret = convert_variable_type(vr_die, pf->tvar, pf->pvar->type); |
561 | pf->tvar->type = strdup(pf->pvar->type); | ||
562 | if (pf->tvar->type == NULL) | ||
563 | ret = -ENOMEM; | ||
564 | } else | ||
565 | ret = convert_variable_type(vr_die, pf->tvar); | ||
566 | } | ||
567 | /* *expr will be cached in libdw. Don't free it. */ | 678 | /* *expr will be cached in libdw. Don't free it. */ |
568 | return ret; | 679 | return ret; |
569 | error: | ||
570 | /* TODO: Support const_value */ | ||
571 | pr_err("Failed to find the location of %s at this address.\n" | ||
572 | " Perhaps, it has been optimized out.\n", pf->pvar->var); | ||
573 | return -ENOENT; | ||
574 | } | 680 | } |
575 | 681 | ||
576 | /* Find a variable in a subprogram die */ | 682 | /* Find a variable in a subprogram die */ |
577 | static int find_variable(Dwarf_Die *sp_die, struct probe_finder *pf) | 683 | static int find_variable(Dwarf_Die *sp_die, struct probe_finder *pf) |
578 | { | 684 | { |
579 | Dwarf_Die vr_die; | 685 | Dwarf_Die vr_die, *scopes; |
580 | char buf[32], *ptr; | 686 | char buf[32], *ptr; |
581 | int ret; | 687 | int ret, nscopes; |
582 | 688 | ||
583 | /* TODO: Support arrays */ | ||
584 | if (pf->pvar->name) | 689 | if (pf->pvar->name) |
585 | pf->tvar->name = strdup(pf->pvar->name); | 690 | pf->tvar->name = strdup(pf->pvar->name); |
586 | else { | 691 | else { |
@@ -600,25 +705,43 @@ static int find_variable(Dwarf_Die *sp_die, struct probe_finder *pf) | |||
600 | pf->tvar->value = strdup(pf->pvar->var); | 705 | pf->tvar->value = strdup(pf->pvar->var); |
601 | if (pf->tvar->value == NULL) | 706 | if (pf->tvar->value == NULL) |
602 | return -ENOMEM; | 707 | return -ENOMEM; |
603 | else | 708 | if (pf->pvar->type) { |
604 | return 0; | 709 | pf->tvar->type = strdup(pf->pvar->type); |
710 | if (pf->tvar->type == NULL) | ||
711 | return -ENOMEM; | ||
712 | } | ||
713 | return 0; | ||
605 | } | 714 | } |
606 | 715 | ||
607 | pr_debug("Searching '%s' variable in context.\n", | 716 | pr_debug("Searching '%s' variable in context.\n", |
608 | pf->pvar->var); | 717 | pf->pvar->var); |
609 | /* Search child die for local variables and parameters. */ | 718 | /* Search child die for local variables and parameters. */ |
610 | if (!die_find_variable(sp_die, pf->pvar->var, &vr_die)) { | 719 | if (die_find_variable(sp_die, pf->pvar->var, &vr_die)) |
720 | ret = convert_variable(&vr_die, pf); | ||
721 | else { | ||
722 | /* Search upper class */ | ||
723 | nscopes = dwarf_getscopes_die(sp_die, &scopes); | ||
724 | if (nscopes > 0) { | ||
725 | ret = dwarf_getscopevar(scopes, nscopes, pf->pvar->var, | ||
726 | 0, NULL, 0, 0, &vr_die); | ||
727 | if (ret >= 0) | ||
728 | ret = convert_variable(&vr_die, pf); | ||
729 | else | ||
730 | ret = -ENOENT; | ||
731 | free(scopes); | ||
732 | } else | ||
733 | ret = -ENOENT; | ||
734 | } | ||
735 | if (ret < 0) | ||
611 | pr_warning("Failed to find '%s' in this function.\n", | 736 | pr_warning("Failed to find '%s' in this function.\n", |
612 | pf->pvar->var); | 737 | pf->pvar->var); |
613 | return -ENOENT; | 738 | return ret; |
614 | } | ||
615 | return convert_variable(&vr_die, pf); | ||
616 | } | 739 | } |
617 | 740 | ||
618 | /* Show a probe point to output buffer */ | 741 | /* Show a probe point to output buffer */ |
619 | static int convert_probe_point(Dwarf_Die *sp_die, struct probe_finder *pf) | 742 | static int convert_probe_point(Dwarf_Die *sp_die, struct probe_finder *pf) |
620 | { | 743 | { |
621 | struct kprobe_trace_event *tev; | 744 | struct probe_trace_event *tev; |
622 | Dwarf_Addr eaddr; | 745 | Dwarf_Addr eaddr; |
623 | Dwarf_Die die_mem; | 746 | Dwarf_Die die_mem; |
624 | const char *name; | 747 | const char *name; |
@@ -683,7 +806,7 @@ static int convert_probe_point(Dwarf_Die *sp_die, struct probe_finder *pf) | |||
683 | 806 | ||
684 | /* Find each argument */ | 807 | /* Find each argument */ |
685 | tev->nargs = pf->pev->nargs; | 808 | tev->nargs = pf->pev->nargs; |
686 | tev->args = zalloc(sizeof(struct kprobe_trace_arg) * tev->nargs); | 809 | tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs); |
687 | if (tev->args == NULL) | 810 | if (tev->args == NULL) |
688 | return -ENOMEM; | 811 | return -ENOMEM; |
689 | for (i = 0; i < pf->pev->nargs; i++) { | 812 | for (i = 0; i < pf->pev->nargs; i++) { |
@@ -897,7 +1020,7 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data) | |||
897 | 1020 | ||
898 | /* Check tag and diename */ | 1021 | /* Check tag and diename */ |
899 | if (dwarf_tag(sp_die) != DW_TAG_subprogram || | 1022 | if (dwarf_tag(sp_die) != DW_TAG_subprogram || |
900 | die_compare_name(sp_die, pp->function) != 0) | 1023 | !die_compare_name(sp_die, pp->function)) |
901 | return DWARF_CB_OK; | 1024 | return DWARF_CB_OK; |
902 | 1025 | ||
903 | pf->fname = dwarf_decl_file(sp_die); | 1026 | pf->fname = dwarf_decl_file(sp_die); |
@@ -940,9 +1063,9 @@ static int find_probe_point_by_func(struct probe_finder *pf) | |||
940 | return _param.retval; | 1063 | return _param.retval; |
941 | } | 1064 | } |
942 | 1065 | ||
943 | /* Find kprobe_trace_events specified by perf_probe_event from debuginfo */ | 1066 | /* Find probe_trace_events specified by perf_probe_event from debuginfo */ |
944 | int find_kprobe_trace_events(int fd, struct perf_probe_event *pev, | 1067 | int find_probe_trace_events(int fd, struct perf_probe_event *pev, |
945 | struct kprobe_trace_event **tevs, int max_tevs) | 1068 | struct probe_trace_event **tevs, int max_tevs) |
946 | { | 1069 | { |
947 | struct probe_finder pf = {.pev = pev, .max_tevs = max_tevs}; | 1070 | struct probe_finder pf = {.pev = pev, .max_tevs = max_tevs}; |
948 | struct perf_probe_point *pp = &pev->point; | 1071 | struct perf_probe_point *pp = &pev->point; |
@@ -952,7 +1075,7 @@ int find_kprobe_trace_events(int fd, struct perf_probe_event *pev, | |||
952 | Dwarf *dbg; | 1075 | Dwarf *dbg; |
953 | int ret = 0; | 1076 | int ret = 0; |
954 | 1077 | ||
955 | pf.tevs = zalloc(sizeof(struct kprobe_trace_event) * max_tevs); | 1078 | pf.tevs = zalloc(sizeof(struct probe_trace_event) * max_tevs); |
956 | if (pf.tevs == NULL) | 1079 | if (pf.tevs == NULL) |
957 | return -ENOMEM; | 1080 | return -ENOMEM; |
958 | *tevs = pf.tevs; | 1081 | *tevs = pf.tevs; |
@@ -1096,7 +1219,7 @@ end: | |||
1096 | static int line_range_add_line(const char *src, unsigned int lineno, | 1219 | static int line_range_add_line(const char *src, unsigned int lineno, |
1097 | struct line_range *lr) | 1220 | struct line_range *lr) |
1098 | { | 1221 | { |
1099 | /* Copy real path */ | 1222 | /* Copy source path */ |
1100 | if (!lr->path) { | 1223 | if (!lr->path) { |
1101 | lr->path = strdup(src); | 1224 | lr->path = strdup(src); |
1102 | if (lr->path == NULL) | 1225 | if (lr->path == NULL) |
@@ -1220,7 +1343,7 @@ static int line_range_search_cb(Dwarf_Die *sp_die, void *data) | |||
1220 | struct line_range *lr = lf->lr; | 1343 | struct line_range *lr = lf->lr; |
1221 | 1344 | ||
1222 | if (dwarf_tag(sp_die) == DW_TAG_subprogram && | 1345 | if (dwarf_tag(sp_die) == DW_TAG_subprogram && |
1223 | die_compare_name(sp_die, lr->function) == 0) { | 1346 | die_compare_name(sp_die, lr->function)) { |
1224 | lf->fname = dwarf_decl_file(sp_die); | 1347 | lf->fname = dwarf_decl_file(sp_die); |
1225 | dwarf_decl_line(sp_die, &lr->offset); | 1348 | dwarf_decl_line(sp_die, &lr->offset); |
1226 | pr_debug("fname: %s, lineno:%d\n", lf->fname, lr->offset); | 1349 | pr_debug("fname: %s, lineno:%d\n", lf->fname, lr->offset); |
@@ -1263,6 +1386,7 @@ int find_line_range(int fd, struct line_range *lr) | |||
1263 | size_t cuhl; | 1386 | size_t cuhl; |
1264 | Dwarf_Die *diep; | 1387 | Dwarf_Die *diep; |
1265 | Dwarf *dbg; | 1388 | Dwarf *dbg; |
1389 | const char *comp_dir; | ||
1266 | 1390 | ||
1267 | dbg = dwarf_begin(fd, DWARF_C_READ); | 1391 | dbg = dwarf_begin(fd, DWARF_C_READ); |
1268 | if (!dbg) { | 1392 | if (!dbg) { |
@@ -1298,7 +1422,18 @@ int find_line_range(int fd, struct line_range *lr) | |||
1298 | } | 1422 | } |
1299 | off = noff; | 1423 | off = noff; |
1300 | } | 1424 | } |
1301 | pr_debug("path: %lx\n", (unsigned long)lr->path); | 1425 | |
1426 | /* Store comp_dir */ | ||
1427 | if (lf.found) { | ||
1428 | comp_dir = cu_get_comp_dir(&lf.cu_die); | ||
1429 | if (comp_dir) { | ||
1430 | lr->comp_dir = strdup(comp_dir); | ||
1431 | if (!lr->comp_dir) | ||
1432 | ret = -ENOMEM; | ||
1433 | } | ||
1434 | } | ||
1435 | |||
1436 | pr_debug("path: %s\n", lr->path); | ||
1302 | dwarf_end(dbg); | 1437 | dwarf_end(dbg); |
1303 | 1438 | ||
1304 | return (ret < 0) ? ret : lf.found; | 1439 | return (ret < 0) ? ret : lf.found; |
diff --git a/tools/perf/util/probe-finder.h b/tools/perf/util/probe-finder.h index e1f61dcd18ff..4507d519f183 100644 --- a/tools/perf/util/probe-finder.h +++ b/tools/perf/util/probe-finder.h | |||
@@ -16,9 +16,9 @@ static inline int is_c_varname(const char *name) | |||
16 | } | 16 | } |
17 | 17 | ||
18 | #ifdef DWARF_SUPPORT | 18 | #ifdef DWARF_SUPPORT |
19 | /* Find kprobe_trace_events specified by perf_probe_event from debuginfo */ | 19 | /* Find probe_trace_events specified by perf_probe_event from debuginfo */ |
20 | extern int find_kprobe_trace_events(int fd, struct perf_probe_event *pev, | 20 | extern int find_probe_trace_events(int fd, struct perf_probe_event *pev, |
21 | struct kprobe_trace_event **tevs, | 21 | struct probe_trace_event **tevs, |
22 | int max_tevs); | 22 | int max_tevs); |
23 | 23 | ||
24 | /* Find a perf_probe_point from debuginfo */ | 24 | /* Find a perf_probe_point from debuginfo */ |
@@ -33,7 +33,7 @@ extern int find_line_range(int fd, struct line_range *lr); | |||
33 | 33 | ||
34 | struct probe_finder { | 34 | struct probe_finder { |
35 | struct perf_probe_event *pev; /* Target probe event */ | 35 | struct perf_probe_event *pev; /* Target probe event */ |
36 | struct kprobe_trace_event *tevs; /* Result trace events */ | 36 | struct probe_trace_event *tevs; /* Result trace events */ |
37 | int ntevs; /* Number of trace events */ | 37 | int ntevs; /* Number of trace events */ |
38 | int max_tevs; /* Max number of trace events */ | 38 | int max_tevs; /* Max number of trace events */ |
39 | 39 | ||
@@ -50,7 +50,7 @@ struct probe_finder { | |||
50 | #endif | 50 | #endif |
51 | Dwarf_Op *fb_ops; /* Frame base attribute */ | 51 | Dwarf_Op *fb_ops; /* Frame base attribute */ |
52 | struct perf_probe_arg *pvar; /* Current target variable */ | 52 | struct perf_probe_arg *pvar; /* Current target variable */ |
53 | struct kprobe_trace_arg *tvar; /* Current result variable */ | 53 | struct probe_trace_arg *tvar; /* Current result variable */ |
54 | }; | 54 | }; |
55 | 55 | ||
56 | struct line_finder { | 56 | struct line_finder { |
diff --git a/tools/perf/util/pstack.h b/tools/perf/util/pstack.h index 5ad07023504b..4cedea59f518 100644 --- a/tools/perf/util/pstack.h +++ b/tools/perf/util/pstack.h | |||
@@ -1,6 +1,8 @@ | |||
1 | #ifndef _PERF_PSTACK_ | 1 | #ifndef _PERF_PSTACK_ |
2 | #define _PERF_PSTACK_ | 2 | #define _PERF_PSTACK_ |
3 | 3 | ||
4 | #include <stdbool.h> | ||
5 | |||
4 | struct pstack; | 6 | struct pstack; |
5 | struct pstack *pstack__new(unsigned short max_nr_entries); | 7 | struct pstack *pstack__new(unsigned short max_nr_entries); |
6 | void pstack__delete(struct pstack *self); | 8 | void pstack__delete(struct pstack *self); |
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index c422cd676313..fa9d652c2dc3 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c | |||
@@ -27,8 +27,10 @@ static int perf_session__open(struct perf_session *self, bool force) | |||
27 | 27 | ||
28 | self->fd = open(self->filename, O_RDONLY); | 28 | self->fd = open(self->filename, O_RDONLY); |
29 | if (self->fd < 0) { | 29 | if (self->fd < 0) { |
30 | pr_err("failed to open file: %s", self->filename); | 30 | int err = errno; |
31 | if (!strcmp(self->filename, "perf.data")) | 31 | |
32 | pr_err("failed to open %s: %s", self->filename, strerror(err)); | ||
33 | if (err == ENOENT && !strcmp(self->filename, "perf.data")) | ||
32 | pr_err(" (try 'perf record' first)"); | 34 | pr_err(" (try 'perf record' first)"); |
33 | pr_err("\n"); | 35 | pr_err("\n"); |
34 | return -errno; | 36 | return -errno; |
@@ -77,6 +79,12 @@ int perf_session__create_kernel_maps(struct perf_session *self) | |||
77 | return ret; | 79 | return ret; |
78 | } | 80 | } |
79 | 81 | ||
82 | static void perf_session__destroy_kernel_maps(struct perf_session *self) | ||
83 | { | ||
84 | machine__destroy_kernel_maps(&self->host_machine); | ||
85 | machines__destroy_guest_kernel_maps(&self->machines); | ||
86 | } | ||
87 | |||
80 | struct perf_session *perf_session__new(const char *filename, int mode, bool force, bool repipe) | 88 | struct perf_session *perf_session__new(const char *filename, int mode, bool force, bool repipe) |
81 | { | 89 | { |
82 | size_t len = filename ? strlen(filename) + 1 : 0; | 90 | size_t len = filename ? strlen(filename) + 1 : 0; |
@@ -94,8 +102,6 @@ struct perf_session *perf_session__new(const char *filename, int mode, bool forc | |||
94 | self->hists_tree = RB_ROOT; | 102 | self->hists_tree = RB_ROOT; |
95 | self->last_match = NULL; | 103 | self->last_match = NULL; |
96 | self->mmap_window = 32; | 104 | self->mmap_window = 32; |
97 | self->cwd = NULL; | ||
98 | self->cwdlen = 0; | ||
99 | self->machines = RB_ROOT; | 105 | self->machines = RB_ROOT; |
100 | self->repipe = repipe; | 106 | self->repipe = repipe; |
101 | INIT_LIST_HEAD(&self->ordered_samples.samples_head); | 107 | INIT_LIST_HEAD(&self->ordered_samples.samples_head); |
@@ -124,16 +130,43 @@ out_delete: | |||
124 | return NULL; | 130 | return NULL; |
125 | } | 131 | } |
126 | 132 | ||
133 | static void perf_session__delete_dead_threads(struct perf_session *self) | ||
134 | { | ||
135 | struct thread *n, *t; | ||
136 | |||
137 | list_for_each_entry_safe(t, n, &self->dead_threads, node) { | ||
138 | list_del(&t->node); | ||
139 | thread__delete(t); | ||
140 | } | ||
141 | } | ||
142 | |||
143 | static void perf_session__delete_threads(struct perf_session *self) | ||
144 | { | ||
145 | struct rb_node *nd = rb_first(&self->threads); | ||
146 | |||
147 | while (nd) { | ||
148 | struct thread *t = rb_entry(nd, struct thread, rb_node); | ||
149 | |||
150 | rb_erase(&t->rb_node, &self->threads); | ||
151 | nd = rb_next(nd); | ||
152 | thread__delete(t); | ||
153 | } | ||
154 | } | ||
155 | |||
127 | void perf_session__delete(struct perf_session *self) | 156 | void perf_session__delete(struct perf_session *self) |
128 | { | 157 | { |
129 | perf_header__exit(&self->header); | 158 | perf_header__exit(&self->header); |
159 | perf_session__destroy_kernel_maps(self); | ||
160 | perf_session__delete_dead_threads(self); | ||
161 | perf_session__delete_threads(self); | ||
162 | machine__exit(&self->host_machine); | ||
130 | close(self->fd); | 163 | close(self->fd); |
131 | free(self->cwd); | ||
132 | free(self); | 164 | free(self); |
133 | } | 165 | } |
134 | 166 | ||
135 | void perf_session__remove_thread(struct perf_session *self, struct thread *th) | 167 | void perf_session__remove_thread(struct perf_session *self, struct thread *th) |
136 | { | 168 | { |
169 | self->last_match = NULL; | ||
137 | rb_erase(&th->rb_node, &self->threads); | 170 | rb_erase(&th->rb_node, &self->threads); |
138 | /* | 171 | /* |
139 | * We may have references to this thread, for instance in some hist_entry | 172 | * We may have references to this thread, for instance in some hist_entry |
@@ -830,23 +863,6 @@ int perf_session__process_events(struct perf_session *self, | |||
830 | if (perf_session__register_idle_thread(self) == NULL) | 863 | if (perf_session__register_idle_thread(self) == NULL) |
831 | return -ENOMEM; | 864 | return -ENOMEM; |
832 | 865 | ||
833 | if (!symbol_conf.full_paths) { | ||
834 | char bf[PATH_MAX]; | ||
835 | |||
836 | if (getcwd(bf, sizeof(bf)) == NULL) { | ||
837 | err = -errno; | ||
838 | out_getcwd_err: | ||
839 | pr_err("failed to get the current directory\n"); | ||
840 | goto out_err; | ||
841 | } | ||
842 | self->cwd = strdup(bf); | ||
843 | if (self->cwd == NULL) { | ||
844 | err = -ENOMEM; | ||
845 | goto out_getcwd_err; | ||
846 | } | ||
847 | self->cwdlen = strlen(self->cwd); | ||
848 | } | ||
849 | |||
850 | if (!self->fd_pipe) | 866 | if (!self->fd_pipe) |
851 | err = __perf_session__process_events(self, | 867 | err = __perf_session__process_events(self, |
852 | self->header.data_offset, | 868 | self->header.data_offset, |
@@ -854,7 +870,7 @@ out_getcwd_err: | |||
854 | self->size, ops); | 870 | self->size, ops); |
855 | else | 871 | else |
856 | err = __perf_session__process_pipe_events(self, ops); | 872 | err = __perf_session__process_pipe_events(self, ops); |
857 | out_err: | 873 | |
858 | return err; | 874 | return err; |
859 | } | 875 | } |
860 | 876 | ||
diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 2316cb5a4116..b62a553cc67d 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c | |||
@@ -1,4 +1,5 @@ | |||
1 | #include "sort.h" | 1 | #include "sort.h" |
2 | #include "hist.h" | ||
2 | 3 | ||
3 | regex_t parent_regex; | 4 | regex_t parent_regex; |
4 | const char default_parent_pattern[] = "^sys_|^do_page_fault"; | 5 | const char default_parent_pattern[] = "^sys_|^do_page_fault"; |
@@ -10,10 +11,6 @@ int sort__has_parent = 0; | |||
10 | 11 | ||
11 | enum sort_type sort__first_dimension; | 12 | enum sort_type sort__first_dimension; |
12 | 13 | ||
13 | unsigned int dsos__col_width; | ||
14 | unsigned int comms__col_width; | ||
15 | unsigned int threads__col_width; | ||
16 | static unsigned int parent_symbol__col_width; | ||
17 | char * field_sep; | 14 | char * field_sep; |
18 | 15 | ||
19 | LIST_HEAD(hist_entry__sort_list); | 16 | LIST_HEAD(hist_entry__sort_list); |
@@ -28,12 +25,14 @@ static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, | |||
28 | size_t size, unsigned int width); | 25 | size_t size, unsigned int width); |
29 | static int hist_entry__parent_snprintf(struct hist_entry *self, char *bf, | 26 | static int hist_entry__parent_snprintf(struct hist_entry *self, char *bf, |
30 | size_t size, unsigned int width); | 27 | size_t size, unsigned int width); |
28 | static int hist_entry__cpu_snprintf(struct hist_entry *self, char *bf, | ||
29 | size_t size, unsigned int width); | ||
31 | 30 | ||
32 | struct sort_entry sort_thread = { | 31 | struct sort_entry sort_thread = { |
33 | .se_header = "Command: Pid", | 32 | .se_header = "Command: Pid", |
34 | .se_cmp = sort__thread_cmp, | 33 | .se_cmp = sort__thread_cmp, |
35 | .se_snprintf = hist_entry__thread_snprintf, | 34 | .se_snprintf = hist_entry__thread_snprintf, |
36 | .se_width = &threads__col_width, | 35 | .se_width_idx = HISTC_THREAD, |
37 | }; | 36 | }; |
38 | 37 | ||
39 | struct sort_entry sort_comm = { | 38 | struct sort_entry sort_comm = { |
@@ -41,27 +40,35 @@ struct sort_entry sort_comm = { | |||
41 | .se_cmp = sort__comm_cmp, | 40 | .se_cmp = sort__comm_cmp, |
42 | .se_collapse = sort__comm_collapse, | 41 | .se_collapse = sort__comm_collapse, |
43 | .se_snprintf = hist_entry__comm_snprintf, | 42 | .se_snprintf = hist_entry__comm_snprintf, |
44 | .se_width = &comms__col_width, | 43 | .se_width_idx = HISTC_COMM, |
45 | }; | 44 | }; |
46 | 45 | ||
47 | struct sort_entry sort_dso = { | 46 | struct sort_entry sort_dso = { |
48 | .se_header = "Shared Object", | 47 | .se_header = "Shared Object", |
49 | .se_cmp = sort__dso_cmp, | 48 | .se_cmp = sort__dso_cmp, |
50 | .se_snprintf = hist_entry__dso_snprintf, | 49 | .se_snprintf = hist_entry__dso_snprintf, |
51 | .se_width = &dsos__col_width, | 50 | .se_width_idx = HISTC_DSO, |
52 | }; | 51 | }; |
53 | 52 | ||
54 | struct sort_entry sort_sym = { | 53 | struct sort_entry sort_sym = { |
55 | .se_header = "Symbol", | 54 | .se_header = "Symbol", |
56 | .se_cmp = sort__sym_cmp, | 55 | .se_cmp = sort__sym_cmp, |
57 | .se_snprintf = hist_entry__sym_snprintf, | 56 | .se_snprintf = hist_entry__sym_snprintf, |
57 | .se_width_idx = HISTC_SYMBOL, | ||
58 | }; | 58 | }; |
59 | 59 | ||
60 | struct sort_entry sort_parent = { | 60 | struct sort_entry sort_parent = { |
61 | .se_header = "Parent symbol", | 61 | .se_header = "Parent symbol", |
62 | .se_cmp = sort__parent_cmp, | 62 | .se_cmp = sort__parent_cmp, |
63 | .se_snprintf = hist_entry__parent_snprintf, | 63 | .se_snprintf = hist_entry__parent_snprintf, |
64 | .se_width = &parent_symbol__col_width, | 64 | .se_width_idx = HISTC_PARENT, |
65 | }; | ||
66 | |||
67 | struct sort_entry sort_cpu = { | ||
68 | .se_header = "CPU", | ||
69 | .se_cmp = sort__cpu_cmp, | ||
70 | .se_snprintf = hist_entry__cpu_snprintf, | ||
71 | .se_width_idx = HISTC_CPU, | ||
65 | }; | 72 | }; |
66 | 73 | ||
67 | struct sort_dimension { | 74 | struct sort_dimension { |
@@ -76,6 +83,7 @@ static struct sort_dimension sort_dimensions[] = { | |||
76 | { .name = "dso", .entry = &sort_dso, }, | 83 | { .name = "dso", .entry = &sort_dso, }, |
77 | { .name = "symbol", .entry = &sort_sym, }, | 84 | { .name = "symbol", .entry = &sort_sym, }, |
78 | { .name = "parent", .entry = &sort_parent, }, | 85 | { .name = "parent", .entry = &sort_parent, }, |
86 | { .name = "cpu", .entry = &sort_cpu, }, | ||
79 | }; | 87 | }; |
80 | 88 | ||
81 | int64_t cmp_null(void *l, void *r) | 89 | int64_t cmp_null(void *l, void *r) |
@@ -188,7 +196,8 @@ static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, | |||
188 | 196 | ||
189 | if (verbose) { | 197 | if (verbose) { |
190 | char o = self->ms.map ? dso__symtab_origin(self->ms.map->dso) : '!'; | 198 | char o = self->ms.map ? dso__symtab_origin(self->ms.map->dso) : '!'; |
191 | ret += repsep_snprintf(bf, size, "%#018llx %c ", self->ip, o); | 199 | ret += repsep_snprintf(bf, size, "%*Lx %c ", |
200 | BITS_PER_LONG / 4, self->ip, o); | ||
192 | } | 201 | } |
193 | 202 | ||
194 | ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", self->level); | 203 | ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", self->level); |
@@ -196,7 +205,8 @@ static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, | |||
196 | ret += repsep_snprintf(bf + ret, size - ret, "%s", | 205 | ret += repsep_snprintf(bf + ret, size - ret, "%s", |
197 | self->ms.sym->name); | 206 | self->ms.sym->name); |
198 | else | 207 | else |
199 | ret += repsep_snprintf(bf + ret, size - ret, "%#016llx", self->ip); | 208 | ret += repsep_snprintf(bf + ret, size - ret, "%*Lx", |
209 | BITS_PER_LONG / 4, self->ip); | ||
200 | 210 | ||
201 | return ret; | 211 | return ret; |
202 | } | 212 | } |
@@ -242,6 +252,20 @@ static int hist_entry__parent_snprintf(struct hist_entry *self, char *bf, | |||
242 | self->parent ? self->parent->name : "[other]"); | 252 | self->parent ? self->parent->name : "[other]"); |
243 | } | 253 | } |
244 | 254 | ||
255 | /* --sort cpu */ | ||
256 | |||
257 | int64_t | ||
258 | sort__cpu_cmp(struct hist_entry *left, struct hist_entry *right) | ||
259 | { | ||
260 | return right->cpu - left->cpu; | ||
261 | } | ||
262 | |||
263 | static int hist_entry__cpu_snprintf(struct hist_entry *self, char *bf, | ||
264 | size_t size, unsigned int width) | ||
265 | { | ||
266 | return repsep_snprintf(bf, size, "%-*d", width, self->cpu); | ||
267 | } | ||
268 | |||
245 | int sort_dimension__add(const char *tok) | 269 | int sort_dimension__add(const char *tok) |
246 | { | 270 | { |
247 | unsigned int i; | 271 | unsigned int i; |
@@ -281,6 +305,8 @@ int sort_dimension__add(const char *tok) | |||
281 | sort__first_dimension = SORT_SYM; | 305 | sort__first_dimension = SORT_SYM; |
282 | else if (!strcmp(sd->name, "parent")) | 306 | else if (!strcmp(sd->name, "parent")) |
283 | sort__first_dimension = SORT_PARENT; | 307 | sort__first_dimension = SORT_PARENT; |
308 | else if (!strcmp(sd->name, "cpu")) | ||
309 | sort__first_dimension = SORT_CPU; | ||
284 | } | 310 | } |
285 | 311 | ||
286 | list_add_tail(&sd->entry->list, &hist_entry__sort_list); | 312 | list_add_tail(&sd->entry->list, &hist_entry__sort_list); |
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 0d61c4082f43..46e531d09e8b 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h | |||
@@ -36,11 +36,14 @@ extern struct sort_entry sort_comm; | |||
36 | extern struct sort_entry sort_dso; | 36 | extern struct sort_entry sort_dso; |
37 | extern struct sort_entry sort_sym; | 37 | extern struct sort_entry sort_sym; |
38 | extern struct sort_entry sort_parent; | 38 | extern struct sort_entry sort_parent; |
39 | extern unsigned int dsos__col_width; | ||
40 | extern unsigned int comms__col_width; | ||
41 | extern unsigned int threads__col_width; | ||
42 | extern enum sort_type sort__first_dimension; | 39 | extern enum sort_type sort__first_dimension; |
43 | 40 | ||
41 | /** | ||
42 | * struct hist_entry - histogram entry | ||
43 | * | ||
44 | * @row_offset - offset from the first callchain expanded to appear on screen | ||
45 | * @nr_rows - rows expanded in callchain, recalculated on folding/unfolding | ||
46 | */ | ||
44 | struct hist_entry { | 47 | struct hist_entry { |
45 | struct rb_node rb_node; | 48 | struct rb_node rb_node; |
46 | u64 period; | 49 | u64 period; |
@@ -51,7 +54,14 @@ struct hist_entry { | |||
51 | struct map_symbol ms; | 54 | struct map_symbol ms; |
52 | struct thread *thread; | 55 | struct thread *thread; |
53 | u64 ip; | 56 | u64 ip; |
57 | s32 cpu; | ||
54 | u32 nr_events; | 58 | u32 nr_events; |
59 | |||
60 | /* XXX These two should move to some tree widget lib */ | ||
61 | u16 row_offset; | ||
62 | u16 nr_rows; | ||
63 | |||
64 | bool init_have_children; | ||
55 | char level; | 65 | char level; |
56 | u8 filtered; | 66 | u8 filtered; |
57 | struct symbol *parent; | 67 | struct symbol *parent; |
@@ -68,7 +78,8 @@ enum sort_type { | |||
68 | SORT_COMM, | 78 | SORT_COMM, |
69 | SORT_DSO, | 79 | SORT_DSO, |
70 | SORT_SYM, | 80 | SORT_SYM, |
71 | SORT_PARENT | 81 | SORT_PARENT, |
82 | SORT_CPU, | ||
72 | }; | 83 | }; |
73 | 84 | ||
74 | /* | 85 | /* |
@@ -84,7 +95,7 @@ struct sort_entry { | |||
84 | int64_t (*se_collapse)(struct hist_entry *, struct hist_entry *); | 95 | int64_t (*se_collapse)(struct hist_entry *, struct hist_entry *); |
85 | int (*se_snprintf)(struct hist_entry *self, char *bf, size_t size, | 96 | int (*se_snprintf)(struct hist_entry *self, char *bf, size_t size, |
86 | unsigned int width); | 97 | unsigned int width); |
87 | unsigned int *se_width; | 98 | u8 se_width_idx; |
88 | bool elide; | 99 | bool elide; |
89 | }; | 100 | }; |
90 | 101 | ||
@@ -104,6 +115,7 @@ extern int64_t sort__comm_collapse(struct hist_entry *, struct hist_entry *); | |||
104 | extern int64_t sort__dso_cmp(struct hist_entry *, struct hist_entry *); | 115 | extern int64_t sort__dso_cmp(struct hist_entry *, struct hist_entry *); |
105 | extern int64_t sort__sym_cmp(struct hist_entry *, struct hist_entry *); | 116 | extern int64_t sort__sym_cmp(struct hist_entry *, struct hist_entry *); |
106 | extern int64_t sort__parent_cmp(struct hist_entry *, struct hist_entry *); | 117 | extern int64_t sort__parent_cmp(struct hist_entry *, struct hist_entry *); |
118 | int64_t sort__cpu_cmp(struct hist_entry *left, struct hist_entry *right); | ||
107 | extern size_t sort__parent_print(FILE *, struct hist_entry *, unsigned int); | 119 | extern size_t sort__parent_print(FILE *, struct hist_entry *, unsigned int); |
108 | extern int sort_dimension__add(const char *); | 120 | extern int sort_dimension__add(const char *); |
109 | void sort_entry__setup_elide(struct sort_entry *self, struct strlist *list, | 121 | void sort_entry__setup_elide(struct sort_entry *self, struct strlist *list, |
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 5b276833e2bf..1a367734e016 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c | |||
@@ -12,6 +12,7 @@ | |||
12 | #include <fcntl.h> | 12 | #include <fcntl.h> |
13 | #include <unistd.h> | 13 | #include <unistd.h> |
14 | #include "build-id.h" | 14 | #include "build-id.h" |
15 | #include "debug.h" | ||
15 | #include "symbol.h" | 16 | #include "symbol.h" |
16 | #include "strlist.h" | 17 | #include "strlist.h" |
17 | 18 | ||
@@ -25,6 +26,8 @@ | |||
25 | #define NT_GNU_BUILD_ID 3 | 26 | #define NT_GNU_BUILD_ID 3 |
26 | #endif | 27 | #endif |
27 | 28 | ||
29 | static bool dso__build_id_equal(const struct dso *self, u8 *build_id); | ||
30 | static int elf_read_build_id(Elf *elf, void *bf, size_t size); | ||
28 | static void dsos__add(struct list_head *head, struct dso *dso); | 31 | static void dsos__add(struct list_head *head, struct dso *dso); |
29 | static struct map *map__new2(u64 start, struct dso *dso, enum map_type type); | 32 | static struct map *map__new2(u64 start, struct dso *dso, enum map_type type); |
30 | static int dso__load_kernel_sym(struct dso *self, struct map *map, | 33 | static int dso__load_kernel_sym(struct dso *self, struct map *map, |
@@ -40,6 +43,14 @@ struct symbol_conf symbol_conf = { | |||
40 | .try_vmlinux_path = true, | 43 | .try_vmlinux_path = true, |
41 | }; | 44 | }; |
42 | 45 | ||
46 | int dso__name_len(const struct dso *self) | ||
47 | { | ||
48 | if (verbose) | ||
49 | return self->long_name_len; | ||
50 | |||
51 | return self->short_name_len; | ||
52 | } | ||
53 | |||
43 | bool dso__loaded(const struct dso *self, enum map_type type) | 54 | bool dso__loaded(const struct dso *self, enum map_type type) |
44 | { | 55 | { |
45 | return self->loaded & (1 << type); | 56 | return self->loaded & (1 << type); |
@@ -120,7 +131,8 @@ static void map_groups__fixup_end(struct map_groups *self) | |||
120 | __map_groups__fixup_end(self, i); | 131 | __map_groups__fixup_end(self, i); |
121 | } | 132 | } |
122 | 133 | ||
123 | static struct symbol *symbol__new(u64 start, u64 len, const char *name) | 134 | static struct symbol *symbol__new(u64 start, u64 len, u8 binding, |
135 | const char *name) | ||
124 | { | 136 | { |
125 | size_t namelen = strlen(name) + 1; | 137 | size_t namelen = strlen(name) + 1; |
126 | struct symbol *self = calloc(1, (symbol_conf.priv_size + | 138 | struct symbol *self = calloc(1, (symbol_conf.priv_size + |
@@ -133,6 +145,7 @@ static struct symbol *symbol__new(u64 start, u64 len, const char *name) | |||
133 | 145 | ||
134 | self->start = start; | 146 | self->start = start; |
135 | self->end = len ? start + len - 1 : start; | 147 | self->end = len ? start + len - 1 : start; |
148 | self->binding = binding; | ||
136 | self->namelen = namelen - 1; | 149 | self->namelen = namelen - 1; |
137 | 150 | ||
138 | pr_debug4("%s: %s %#Lx-%#Lx\n", __func__, name, start, self->end); | 151 | pr_debug4("%s: %s %#Lx-%#Lx\n", __func__, name, start, self->end); |
@@ -149,8 +162,11 @@ void symbol__delete(struct symbol *self) | |||
149 | 162 | ||
150 | static size_t symbol__fprintf(struct symbol *self, FILE *fp) | 163 | static size_t symbol__fprintf(struct symbol *self, FILE *fp) |
151 | { | 164 | { |
152 | return fprintf(fp, " %llx-%llx %s\n", | 165 | return fprintf(fp, " %llx-%llx %c %s\n", |
153 | self->start, self->end, self->name); | 166 | self->start, self->end, |
167 | self->binding == STB_GLOBAL ? 'g' : | ||
168 | self->binding == STB_LOCAL ? 'l' : 'w', | ||
169 | self->name); | ||
154 | } | 170 | } |
155 | 171 | ||
156 | void dso__set_long_name(struct dso *self, char *name) | 172 | void dso__set_long_name(struct dso *self, char *name) |
@@ -215,7 +231,9 @@ void dso__delete(struct dso *self) | |||
215 | int i; | 231 | int i; |
216 | for (i = 0; i < MAP__NR_TYPES; ++i) | 232 | for (i = 0; i < MAP__NR_TYPES; ++i) |
217 | symbols__delete(&self->symbols[i]); | 233 | symbols__delete(&self->symbols[i]); |
218 | if (self->long_name != self->name) | 234 | if (self->sname_alloc) |
235 | free((char *)self->short_name); | ||
236 | if (self->lname_alloc) | ||
219 | free(self->long_name); | 237 | free(self->long_name); |
220 | free(self); | 238 | free(self); |
221 | } | 239 | } |
@@ -440,6 +458,14 @@ struct process_kallsyms_args { | |||
440 | struct dso *dso; | 458 | struct dso *dso; |
441 | }; | 459 | }; |
442 | 460 | ||
461 | static u8 kallsyms2elf_type(char type) | ||
462 | { | ||
463 | if (type == 'W') | ||
464 | return STB_WEAK; | ||
465 | |||
466 | return isupper(type) ? STB_GLOBAL : STB_LOCAL; | ||
467 | } | ||
468 | |||
443 | static int map__process_kallsym_symbol(void *arg, const char *name, | 469 | static int map__process_kallsym_symbol(void *arg, const char *name, |
444 | char type, u64 start) | 470 | char type, u64 start) |
445 | { | 471 | { |
@@ -453,7 +479,7 @@ static int map__process_kallsym_symbol(void *arg, const char *name, | |||
453 | /* | 479 | /* |
454 | * Will fix up the end later, when we have all symbols sorted. | 480 | * Will fix up the end later, when we have all symbols sorted. |
455 | */ | 481 | */ |
456 | sym = symbol__new(start, 0, name); | 482 | sym = symbol__new(start, 0, kallsyms2elf_type(type), name); |
457 | 483 | ||
458 | if (sym == NULL) | 484 | if (sym == NULL) |
459 | return -ENOMEM; | 485 | return -ENOMEM; |
@@ -648,7 +674,7 @@ static int dso__load_perf_map(struct dso *self, struct map *map, | |||
648 | if (len + 2 >= line_len) | 674 | if (len + 2 >= line_len) |
649 | continue; | 675 | continue; |
650 | 676 | ||
651 | sym = symbol__new(start, size, line + len); | 677 | sym = symbol__new(start, size, STB_GLOBAL, line + len); |
652 | 678 | ||
653 | if (sym == NULL) | 679 | if (sym == NULL) |
654 | goto out_delete_line; | 680 | goto out_delete_line; |
@@ -860,7 +886,7 @@ static int dso__synthesize_plt_symbols(struct dso *self, struct map *map, | |||
860 | "%s@plt", elf_sym__name(&sym, symstrs)); | 886 | "%s@plt", elf_sym__name(&sym, symstrs)); |
861 | 887 | ||
862 | f = symbol__new(plt_offset, shdr_plt.sh_entsize, | 888 | f = symbol__new(plt_offset, shdr_plt.sh_entsize, |
863 | sympltname); | 889 | STB_GLOBAL, sympltname); |
864 | if (!f) | 890 | if (!f) |
865 | goto out_elf_end; | 891 | goto out_elf_end; |
866 | 892 | ||
@@ -882,7 +908,7 @@ static int dso__synthesize_plt_symbols(struct dso *self, struct map *map, | |||
882 | "%s@plt", elf_sym__name(&sym, symstrs)); | 908 | "%s@plt", elf_sym__name(&sym, symstrs)); |
883 | 909 | ||
884 | f = symbol__new(plt_offset, shdr_plt.sh_entsize, | 910 | f = symbol__new(plt_offset, shdr_plt.sh_entsize, |
885 | sympltname); | 911 | STB_GLOBAL, sympltname); |
886 | if (!f) | 912 | if (!f) |
887 | goto out_elf_end; | 913 | goto out_elf_end; |
888 | 914 | ||
@@ -933,8 +959,28 @@ static bool elf_sec__is_a(GElf_Shdr *self, Elf_Data *secstrs, enum map_type type | |||
933 | } | 959 | } |
934 | } | 960 | } |
935 | 961 | ||
962 | static size_t elf_addr_to_index(Elf *elf, GElf_Addr addr) | ||
963 | { | ||
964 | Elf_Scn *sec = NULL; | ||
965 | GElf_Shdr shdr; | ||
966 | size_t cnt = 1; | ||
967 | |||
968 | while ((sec = elf_nextscn(elf, sec)) != NULL) { | ||
969 | gelf_getshdr(sec, &shdr); | ||
970 | |||
971 | if ((addr >= shdr.sh_addr) && | ||
972 | (addr < (shdr.sh_addr + shdr.sh_size))) | ||
973 | return cnt; | ||
974 | |||
975 | ++cnt; | ||
976 | } | ||
977 | |||
978 | return -1; | ||
979 | } | ||
980 | |||
936 | static int dso__load_sym(struct dso *self, struct map *map, const char *name, | 981 | static int dso__load_sym(struct dso *self, struct map *map, const char *name, |
937 | int fd, symbol_filter_t filter, int kmodule) | 982 | int fd, symbol_filter_t filter, int kmodule, |
983 | int want_symtab) | ||
938 | { | 984 | { |
939 | struct kmap *kmap = self->kernel ? map__kmap(map) : NULL; | 985 | struct kmap *kmap = self->kernel ? map__kmap(map) : NULL; |
940 | struct map *curr_map = map; | 986 | struct map *curr_map = map; |
@@ -944,31 +990,51 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, | |||
944 | int err = -1; | 990 | int err = -1; |
945 | uint32_t idx; | 991 | uint32_t idx; |
946 | GElf_Ehdr ehdr; | 992 | GElf_Ehdr ehdr; |
947 | GElf_Shdr shdr; | 993 | GElf_Shdr shdr, opdshdr; |
948 | Elf_Data *syms; | 994 | Elf_Data *syms, *opddata = NULL; |
949 | GElf_Sym sym; | 995 | GElf_Sym sym; |
950 | Elf_Scn *sec, *sec_strndx; | 996 | Elf_Scn *sec, *sec_strndx, *opdsec; |
951 | Elf *elf; | 997 | Elf *elf; |
952 | int nr = 0; | 998 | int nr = 0; |
999 | size_t opdidx = 0; | ||
953 | 1000 | ||
954 | elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); | 1001 | elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); |
955 | if (elf == NULL) { | 1002 | if (elf == NULL) { |
956 | pr_err("%s: cannot read %s ELF file.\n", __func__, name); | 1003 | pr_debug("%s: cannot read %s ELF file.\n", __func__, name); |
957 | goto out_close; | 1004 | goto out_close; |
958 | } | 1005 | } |
959 | 1006 | ||
960 | if (gelf_getehdr(elf, &ehdr) == NULL) { | 1007 | if (gelf_getehdr(elf, &ehdr) == NULL) { |
961 | pr_err("%s: cannot get elf header.\n", __func__); | 1008 | pr_debug("%s: cannot get elf header.\n", __func__); |
962 | goto out_elf_end; | 1009 | goto out_elf_end; |
963 | } | 1010 | } |
964 | 1011 | ||
1012 | /* Always reject images with a mismatched build-id: */ | ||
1013 | if (self->has_build_id) { | ||
1014 | u8 build_id[BUILD_ID_SIZE]; | ||
1015 | |||
1016 | if (elf_read_build_id(elf, build_id, | ||
1017 | BUILD_ID_SIZE) != BUILD_ID_SIZE) | ||
1018 | goto out_elf_end; | ||
1019 | |||
1020 | if (!dso__build_id_equal(self, build_id)) | ||
1021 | goto out_elf_end; | ||
1022 | } | ||
1023 | |||
965 | sec = elf_section_by_name(elf, &ehdr, &shdr, ".symtab", NULL); | 1024 | sec = elf_section_by_name(elf, &ehdr, &shdr, ".symtab", NULL); |
966 | if (sec == NULL) { | 1025 | if (sec == NULL) { |
1026 | if (want_symtab) | ||
1027 | goto out_elf_end; | ||
1028 | |||
967 | sec = elf_section_by_name(elf, &ehdr, &shdr, ".dynsym", NULL); | 1029 | sec = elf_section_by_name(elf, &ehdr, &shdr, ".dynsym", NULL); |
968 | if (sec == NULL) | 1030 | if (sec == NULL) |
969 | goto out_elf_end; | 1031 | goto out_elf_end; |
970 | } | 1032 | } |
971 | 1033 | ||
1034 | opdsec = elf_section_by_name(elf, &ehdr, &opdshdr, ".opd", &opdidx); | ||
1035 | if (opdsec) | ||
1036 | opddata = elf_rawdata(opdsec, NULL); | ||
1037 | |||
972 | syms = elf_getdata(sec, NULL); | 1038 | syms = elf_getdata(sec, NULL); |
973 | if (syms == NULL) | 1039 | if (syms == NULL) |
974 | goto out_elf_end; | 1040 | goto out_elf_end; |
@@ -1013,6 +1079,23 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, | |||
1013 | if (!is_label && !elf_sym__is_a(&sym, map->type)) | 1079 | if (!is_label && !elf_sym__is_a(&sym, map->type)) |
1014 | continue; | 1080 | continue; |
1015 | 1081 | ||
1082 | /* Reject ARM ELF "mapping symbols": these aren't unique and | ||
1083 | * don't identify functions, so will confuse the profile | ||
1084 | * output: */ | ||
1085 | if (ehdr.e_machine == EM_ARM) { | ||
1086 | if (!strcmp(elf_name, "$a") || | ||
1087 | !strcmp(elf_name, "$d") || | ||
1088 | !strcmp(elf_name, "$t")) | ||
1089 | continue; | ||
1090 | } | ||
1091 | |||
1092 | if (opdsec && sym.st_shndx == opdidx) { | ||
1093 | u32 offset = sym.st_value - opdshdr.sh_addr; | ||
1094 | u64 *opd = opddata->d_buf + offset; | ||
1095 | sym.st_value = *opd; | ||
1096 | sym.st_shndx = elf_addr_to_index(elf, sym.st_value); | ||
1097 | } | ||
1098 | |||
1016 | sec = elf_getscn(elf, sym.st_shndx); | 1099 | sec = elf_getscn(elf, sym.st_shndx); |
1017 | if (!sec) | 1100 | if (!sec) |
1018 | goto out_elf_end; | 1101 | goto out_elf_end; |
@@ -1086,7 +1169,8 @@ static int dso__load_sym(struct dso *self, struct map *map, const char *name, | |||
1086 | if (demangled != NULL) | 1169 | if (demangled != NULL) |
1087 | elf_name = demangled; | 1170 | elf_name = demangled; |
1088 | new_symbol: | 1171 | new_symbol: |
1089 | f = symbol__new(sym.st_value, sym.st_size, elf_name); | 1172 | f = symbol__new(sym.st_value, sym.st_size, |
1173 | GELF_ST_BIND(sym.st_info), elf_name); | ||
1090 | free(demangled); | 1174 | free(demangled); |
1091 | if (!f) | 1175 | if (!f) |
1092 | goto out_elf_end; | 1176 | goto out_elf_end; |
@@ -1151,37 +1235,26 @@ bool __dsos__read_build_ids(struct list_head *head, bool with_hits) | |||
1151 | */ | 1235 | */ |
1152 | #define NOTE_ALIGN(n) (((n) + 3) & -4U) | 1236 | #define NOTE_ALIGN(n) (((n) + 3) & -4U) |
1153 | 1237 | ||
1154 | int filename__read_build_id(const char *filename, void *bf, size_t size) | 1238 | static int elf_read_build_id(Elf *elf, void *bf, size_t size) |
1155 | { | 1239 | { |
1156 | int fd, err = -1; | 1240 | int err = -1; |
1157 | GElf_Ehdr ehdr; | 1241 | GElf_Ehdr ehdr; |
1158 | GElf_Shdr shdr; | 1242 | GElf_Shdr shdr; |
1159 | Elf_Data *data; | 1243 | Elf_Data *data; |
1160 | Elf_Scn *sec; | 1244 | Elf_Scn *sec; |
1161 | Elf_Kind ek; | 1245 | Elf_Kind ek; |
1162 | void *ptr; | 1246 | void *ptr; |
1163 | Elf *elf; | ||
1164 | 1247 | ||
1165 | if (size < BUILD_ID_SIZE) | 1248 | if (size < BUILD_ID_SIZE) |
1166 | goto out; | 1249 | goto out; |
1167 | 1250 | ||
1168 | fd = open(filename, O_RDONLY); | ||
1169 | if (fd < 0) | ||
1170 | goto out; | ||
1171 | |||
1172 | elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); | ||
1173 | if (elf == NULL) { | ||
1174 | pr_debug2("%s: cannot read %s ELF file.\n", __func__, filename); | ||
1175 | goto out_close; | ||
1176 | } | ||
1177 | |||
1178 | ek = elf_kind(elf); | 1251 | ek = elf_kind(elf); |
1179 | if (ek != ELF_K_ELF) | 1252 | if (ek != ELF_K_ELF) |
1180 | goto out_elf_end; | 1253 | goto out; |
1181 | 1254 | ||
1182 | if (gelf_getehdr(elf, &ehdr) == NULL) { | 1255 | if (gelf_getehdr(elf, &ehdr) == NULL) { |
1183 | pr_err("%s: cannot get elf header.\n", __func__); | 1256 | pr_err("%s: cannot get elf header.\n", __func__); |
1184 | goto out_elf_end; | 1257 | goto out; |
1185 | } | 1258 | } |
1186 | 1259 | ||
1187 | sec = elf_section_by_name(elf, &ehdr, &shdr, | 1260 | sec = elf_section_by_name(elf, &ehdr, &shdr, |
@@ -1190,12 +1263,12 @@ int filename__read_build_id(const char *filename, void *bf, size_t size) | |||
1190 | sec = elf_section_by_name(elf, &ehdr, &shdr, | 1263 | sec = elf_section_by_name(elf, &ehdr, &shdr, |
1191 | ".notes", NULL); | 1264 | ".notes", NULL); |
1192 | if (sec == NULL) | 1265 | if (sec == NULL) |
1193 | goto out_elf_end; | 1266 | goto out; |
1194 | } | 1267 | } |
1195 | 1268 | ||
1196 | data = elf_getdata(sec, NULL); | 1269 | data = elf_getdata(sec, NULL); |
1197 | if (data == NULL) | 1270 | if (data == NULL) |
1198 | goto out_elf_end; | 1271 | goto out; |
1199 | 1272 | ||
1200 | ptr = data->d_buf; | 1273 | ptr = data->d_buf; |
1201 | while (ptr < (data->d_buf + data->d_size)) { | 1274 | while (ptr < (data->d_buf + data->d_size)) { |
@@ -1217,7 +1290,31 @@ int filename__read_build_id(const char *filename, void *bf, size_t size) | |||
1217 | } | 1290 | } |
1218 | ptr += descsz; | 1291 | ptr += descsz; |
1219 | } | 1292 | } |
1220 | out_elf_end: | 1293 | |
1294 | out: | ||
1295 | return err; | ||
1296 | } | ||
1297 | |||
1298 | int filename__read_build_id(const char *filename, void *bf, size_t size) | ||
1299 | { | ||
1300 | int fd, err = -1; | ||
1301 | Elf *elf; | ||
1302 | |||
1303 | if (size < BUILD_ID_SIZE) | ||
1304 | goto out; | ||
1305 | |||
1306 | fd = open(filename, O_RDONLY); | ||
1307 | if (fd < 0) | ||
1308 | goto out; | ||
1309 | |||
1310 | elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); | ||
1311 | if (elf == NULL) { | ||
1312 | pr_debug2("%s: cannot read %s ELF file.\n", __func__, filename); | ||
1313 | goto out_close; | ||
1314 | } | ||
1315 | |||
1316 | err = elf_read_build_id(elf, bf, size); | ||
1317 | |||
1221 | elf_end(elf); | 1318 | elf_end(elf); |
1222 | out_close: | 1319 | out_close: |
1223 | close(fd); | 1320 | close(fd); |
@@ -1293,11 +1390,11 @@ int dso__load(struct dso *self, struct map *map, symbol_filter_t filter) | |||
1293 | { | 1390 | { |
1294 | int size = PATH_MAX; | 1391 | int size = PATH_MAX; |
1295 | char *name; | 1392 | char *name; |
1296 | u8 build_id[BUILD_ID_SIZE]; | ||
1297 | int ret = -1; | 1393 | int ret = -1; |
1298 | int fd; | 1394 | int fd; |
1299 | struct machine *machine; | 1395 | struct machine *machine; |
1300 | const char *root_dir; | 1396 | const char *root_dir; |
1397 | int want_symtab; | ||
1301 | 1398 | ||
1302 | dso__set_loaded(self, map->type); | 1399 | dso__set_loaded(self, map->type); |
1303 | 1400 | ||
@@ -1324,13 +1421,18 @@ int dso__load(struct dso *self, struct map *map, symbol_filter_t filter) | |||
1324 | return ret; | 1421 | return ret; |
1325 | } | 1422 | } |
1326 | 1423 | ||
1327 | self->origin = DSO__ORIG_BUILD_ID_CACHE; | 1424 | /* Iterate over candidate debug images. |
1328 | if (dso__build_id_filename(self, name, size) != NULL) | 1425 | * On the first pass, only load images if they have a full symtab. |
1329 | goto open_file; | 1426 | * Failing that, do a second pass where we accept .dynsym also |
1330 | more: | 1427 | */ |
1331 | do { | 1428 | for (self->origin = DSO__ORIG_BUILD_ID_CACHE, want_symtab = 1; |
1332 | self->origin++; | 1429 | self->origin != DSO__ORIG_NOT_FOUND; |
1430 | self->origin++) { | ||
1333 | switch (self->origin) { | 1431 | switch (self->origin) { |
1432 | case DSO__ORIG_BUILD_ID_CACHE: | ||
1433 | if (dso__build_id_filename(self, name, size) == NULL) | ||
1434 | continue; | ||
1435 | break; | ||
1334 | case DSO__ORIG_FEDORA: | 1436 | case DSO__ORIG_FEDORA: |
1335 | snprintf(name, size, "/usr/lib/debug%s.debug", | 1437 | snprintf(name, size, "/usr/lib/debug%s.debug", |
1336 | self->long_name); | 1438 | self->long_name); |
@@ -1339,21 +1441,20 @@ more: | |||
1339 | snprintf(name, size, "/usr/lib/debug%s", | 1441 | snprintf(name, size, "/usr/lib/debug%s", |
1340 | self->long_name); | 1442 | self->long_name); |
1341 | break; | 1443 | break; |
1342 | case DSO__ORIG_BUILDID: | 1444 | case DSO__ORIG_BUILDID: { |
1343 | if (filename__read_build_id(self->long_name, build_id, | 1445 | char build_id_hex[BUILD_ID_SIZE * 2 + 1]; |
1344 | sizeof(build_id))) { | 1446 | |
1345 | char build_id_hex[BUILD_ID_SIZE * 2 + 1]; | 1447 | if (!self->has_build_id) |
1346 | build_id__sprintf(build_id, sizeof(build_id), | 1448 | continue; |
1347 | build_id_hex); | 1449 | |
1348 | snprintf(name, size, | 1450 | build_id__sprintf(self->build_id, |
1349 | "/usr/lib/debug/.build-id/%.2s/%s.debug", | 1451 | sizeof(self->build_id), |
1350 | build_id_hex, build_id_hex + 2); | 1452 | build_id_hex); |
1351 | if (self->has_build_id) | 1453 | snprintf(name, size, |
1352 | goto compare_build_id; | 1454 | "/usr/lib/debug/.build-id/%.2s/%s.debug", |
1353 | break; | 1455 | build_id_hex, build_id_hex + 2); |
1354 | } | 1456 | } |
1355 | self->origin++; | 1457 | break; |
1356 | /* Fall thru */ | ||
1357 | case DSO__ORIG_DSO: | 1458 | case DSO__ORIG_DSO: |
1358 | snprintf(name, size, "%s", self->long_name); | 1459 | snprintf(name, size, "%s", self->long_name); |
1359 | break; | 1460 | break; |
@@ -1366,36 +1467,41 @@ more: | |||
1366 | break; | 1467 | break; |
1367 | 1468 | ||
1368 | default: | 1469 | default: |
1369 | goto out; | 1470 | /* |
1471 | * If we wanted a full symtab but no image had one, | ||
1472 | * relax our requirements and repeat the search. | ||
1473 | */ | ||
1474 | if (want_symtab) { | ||
1475 | want_symtab = 0; | ||
1476 | self->origin = DSO__ORIG_BUILD_ID_CACHE; | ||
1477 | } else | ||
1478 | continue; | ||
1370 | } | 1479 | } |
1371 | 1480 | ||
1372 | if (self->has_build_id) { | 1481 | /* Name is now the name of the next image to try */ |
1373 | if (filename__read_build_id(name, build_id, | ||
1374 | sizeof(build_id)) < 0) | ||
1375 | goto more; | ||
1376 | compare_build_id: | ||
1377 | if (!dso__build_id_equal(self, build_id)) | ||
1378 | goto more; | ||
1379 | } | ||
1380 | open_file: | ||
1381 | fd = open(name, O_RDONLY); | 1482 | fd = open(name, O_RDONLY); |
1382 | } while (fd < 0); | 1483 | if (fd < 0) |
1484 | continue; | ||
1383 | 1485 | ||
1384 | ret = dso__load_sym(self, map, name, fd, filter, 0); | 1486 | ret = dso__load_sym(self, map, name, fd, filter, 0, |
1385 | close(fd); | 1487 | want_symtab); |
1488 | close(fd); | ||
1386 | 1489 | ||
1387 | /* | 1490 | /* |
1388 | * Some people seem to have debuginfo files _WITHOUT_ debug info!?!? | 1491 | * Some people seem to have debuginfo files _WITHOUT_ debug |
1389 | */ | 1492 | * info!?!? |
1390 | if (!ret) | 1493 | */ |
1391 | goto more; | 1494 | if (!ret) |
1495 | continue; | ||
1392 | 1496 | ||
1393 | if (ret > 0) { | 1497 | if (ret > 0) { |
1394 | int nr_plt = dso__synthesize_plt_symbols(self, map, filter); | 1498 | int nr_plt = dso__synthesize_plt_symbols(self, map, filter); |
1395 | if (nr_plt > 0) | 1499 | if (nr_plt > 0) |
1396 | ret += nr_plt; | 1500 | ret += nr_plt; |
1501 | break; | ||
1502 | } | ||
1397 | } | 1503 | } |
1398 | out: | 1504 | |
1399 | free(name); | 1505 | free(name); |
1400 | if (ret < 0 && strstr(self->name, " (deleted)") != NULL) | 1506 | if (ret < 0 && strstr(self->name, " (deleted)") != NULL) |
1401 | return 0; | 1507 | return 0; |
@@ -1494,6 +1600,7 @@ static int map_groups__set_modules_path_dir(struct map_groups *self, | |||
1494 | goto out; | 1600 | goto out; |
1495 | } | 1601 | } |
1496 | dso__set_long_name(map->dso, long_name); | 1602 | dso__set_long_name(map->dso, long_name); |
1603 | map->dso->lname_alloc = 1; | ||
1497 | dso__kernel_module_get_build_id(map->dso, ""); | 1604 | dso__kernel_module_get_build_id(map->dso, ""); |
1498 | } | 1605 | } |
1499 | } | 1606 | } |
@@ -1656,36 +1763,12 @@ static int dso__load_vmlinux(struct dso *self, struct map *map, | |||
1656 | { | 1763 | { |
1657 | int err = -1, fd; | 1764 | int err = -1, fd; |
1658 | 1765 | ||
1659 | if (self->has_build_id) { | ||
1660 | u8 build_id[BUILD_ID_SIZE]; | ||
1661 | |||
1662 | if (filename__read_build_id(vmlinux, build_id, | ||
1663 | sizeof(build_id)) < 0) { | ||
1664 | pr_debug("No build_id in %s, ignoring it\n", vmlinux); | ||
1665 | return -1; | ||
1666 | } | ||
1667 | if (!dso__build_id_equal(self, build_id)) { | ||
1668 | char expected_build_id[BUILD_ID_SIZE * 2 + 1], | ||
1669 | vmlinux_build_id[BUILD_ID_SIZE * 2 + 1]; | ||
1670 | |||
1671 | build_id__sprintf(self->build_id, | ||
1672 | sizeof(self->build_id), | ||
1673 | expected_build_id); | ||
1674 | build_id__sprintf(build_id, sizeof(build_id), | ||
1675 | vmlinux_build_id); | ||
1676 | pr_debug("build_id in %s is %s while expected is %s, " | ||
1677 | "ignoring it\n", vmlinux, vmlinux_build_id, | ||
1678 | expected_build_id); | ||
1679 | return -1; | ||
1680 | } | ||
1681 | } | ||
1682 | |||
1683 | fd = open(vmlinux, O_RDONLY); | 1766 | fd = open(vmlinux, O_RDONLY); |
1684 | if (fd < 0) | 1767 | if (fd < 0) |
1685 | return -1; | 1768 | return -1; |
1686 | 1769 | ||
1687 | dso__set_loaded(self, map->type); | 1770 | dso__set_loaded(self, map->type); |
1688 | err = dso__load_sym(self, map, vmlinux, fd, filter, 0); | 1771 | err = dso__load_sym(self, map, vmlinux, fd, filter, 0, 0); |
1689 | close(fd); | 1772 | close(fd); |
1690 | 1773 | ||
1691 | if (err > 0) | 1774 | if (err > 0) |
@@ -2048,6 +2131,36 @@ int __machine__create_kernel_maps(struct machine *self, struct dso *kernel) | |||
2048 | return 0; | 2131 | return 0; |
2049 | } | 2132 | } |
2050 | 2133 | ||
2134 | void machine__destroy_kernel_maps(struct machine *self) | ||
2135 | { | ||
2136 | enum map_type type; | ||
2137 | |||
2138 | for (type = 0; type < MAP__NR_TYPES; ++type) { | ||
2139 | struct kmap *kmap; | ||
2140 | |||
2141 | if (self->vmlinux_maps[type] == NULL) | ||
2142 | continue; | ||
2143 | |||
2144 | kmap = map__kmap(self->vmlinux_maps[type]); | ||
2145 | map_groups__remove(&self->kmaps, self->vmlinux_maps[type]); | ||
2146 | if (kmap->ref_reloc_sym) { | ||
2147 | /* | ||
2148 | * ref_reloc_sym is shared among all maps, so free just | ||
2149 | * on one of them. | ||
2150 | */ | ||
2151 | if (type == MAP__FUNCTION) { | ||
2152 | free((char *)kmap->ref_reloc_sym->name); | ||
2153 | kmap->ref_reloc_sym->name = NULL; | ||
2154 | free(kmap->ref_reloc_sym); | ||
2155 | } | ||
2156 | kmap->ref_reloc_sym = NULL; | ||
2157 | } | ||
2158 | |||
2159 | map__delete(self->vmlinux_maps[type]); | ||
2160 | self->vmlinux_maps[type] = NULL; | ||
2161 | } | ||
2162 | } | ||
2163 | |||
2051 | int machine__create_kernel_maps(struct machine *self) | 2164 | int machine__create_kernel_maps(struct machine *self) |
2052 | { | 2165 | { |
2053 | struct dso *kernel = machine__create_kernel(self); | 2166 | struct dso *kernel = machine__create_kernel(self); |
@@ -2189,6 +2302,15 @@ out_free_comm_list: | |||
2189 | return -1; | 2302 | return -1; |
2190 | } | 2303 | } |
2191 | 2304 | ||
2305 | void symbol__exit(void) | ||
2306 | { | ||
2307 | strlist__delete(symbol_conf.sym_list); | ||
2308 | strlist__delete(symbol_conf.dso_list); | ||
2309 | strlist__delete(symbol_conf.comm_list); | ||
2310 | vmlinux_path__exit(); | ||
2311 | symbol_conf.sym_list = symbol_conf.dso_list = symbol_conf.comm_list = NULL; | ||
2312 | } | ||
2313 | |||
2192 | int machines__create_kernel_maps(struct rb_root *self, pid_t pid) | 2314 | int machines__create_kernel_maps(struct rb_root *self, pid_t pid) |
2193 | { | 2315 | { |
2194 | struct machine *machine = machines__findnew(self, pid); | 2316 | struct machine *machine = machines__findnew(self, pid); |
@@ -2283,6 +2405,19 @@ failure: | |||
2283 | return ret; | 2405 | return ret; |
2284 | } | 2406 | } |
2285 | 2407 | ||
2408 | void machines__destroy_guest_kernel_maps(struct rb_root *self) | ||
2409 | { | ||
2410 | struct rb_node *next = rb_first(self); | ||
2411 | |||
2412 | while (next) { | ||
2413 | struct machine *pos = rb_entry(next, struct machine, rb_node); | ||
2414 | |||
2415 | next = rb_next(&pos->rb_node); | ||
2416 | rb_erase(&pos->rb_node, self); | ||
2417 | machine__delete(pos); | ||
2418 | } | ||
2419 | } | ||
2420 | |||
2286 | int machine__load_kallsyms(struct machine *self, const char *filename, | 2421 | int machine__load_kallsyms(struct machine *self, const char *filename, |
2287 | enum map_type type, symbol_filter_t filter) | 2422 | enum map_type type, symbol_filter_t filter) |
2288 | { | 2423 | { |
diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 5e02d2c17154..b7a8da4af5a0 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h | |||
@@ -9,8 +9,6 @@ | |||
9 | #include <linux/rbtree.h> | 9 | #include <linux/rbtree.h> |
10 | #include <stdio.h> | 10 | #include <stdio.h> |
11 | 11 | ||
12 | #define DEBUG_CACHE_DIR ".debug" | ||
13 | |||
14 | #ifdef HAVE_CPLUS_DEMANGLE | 12 | #ifdef HAVE_CPLUS_DEMANGLE |
15 | extern char *cplus_demangle(const char *, int); | 13 | extern char *cplus_demangle(const char *, int); |
16 | 14 | ||
@@ -55,6 +53,7 @@ struct symbol { | |||
55 | u64 start; | 53 | u64 start; |
56 | u64 end; | 54 | u64 end; |
57 | u16 namelen; | 55 | u16 namelen; |
56 | u8 binding; | ||
58 | char name[0]; | 57 | char name[0]; |
59 | }; | 58 | }; |
60 | 59 | ||
@@ -70,9 +69,9 @@ struct symbol_conf { | |||
70 | show_nr_samples, | 69 | show_nr_samples, |
71 | use_callchain, | 70 | use_callchain, |
72 | exclude_other, | 71 | exclude_other, |
73 | full_paths, | ||
74 | show_cpu_utilization; | 72 | show_cpu_utilization; |
75 | const char *vmlinux_name, | 73 | const char *vmlinux_name, |
74 | *source_prefix, | ||
76 | *field_sep; | 75 | *field_sep; |
77 | const char *default_guest_vmlinux_name, | 76 | const char *default_guest_vmlinux_name, |
78 | *default_guest_kallsyms, | 77 | *default_guest_kallsyms, |
@@ -103,6 +102,8 @@ struct ref_reloc_sym { | |||
103 | struct map_symbol { | 102 | struct map_symbol { |
104 | struct map *map; | 103 | struct map *map; |
105 | struct symbol *sym; | 104 | struct symbol *sym; |
105 | bool unfolded; | ||
106 | bool has_children; | ||
106 | }; | 107 | }; |
107 | 108 | ||
108 | struct addr_location { | 109 | struct addr_location { |
@@ -112,7 +113,8 @@ struct addr_location { | |||
112 | u64 addr; | 113 | u64 addr; |
113 | char level; | 114 | char level; |
114 | bool filtered; | 115 | bool filtered; |
115 | unsigned int cpumode; | 116 | u8 cpumode; |
117 | s32 cpu; | ||
116 | }; | 118 | }; |
117 | 119 | ||
118 | enum dso_kernel_type { | 120 | enum dso_kernel_type { |
@@ -125,12 +127,14 @@ struct dso { | |||
125 | struct list_head node; | 127 | struct list_head node; |
126 | struct rb_root symbols[MAP__NR_TYPES]; | 128 | struct rb_root symbols[MAP__NR_TYPES]; |
127 | struct rb_root symbol_names[MAP__NR_TYPES]; | 129 | struct rb_root symbol_names[MAP__NR_TYPES]; |
130 | enum dso_kernel_type kernel; | ||
128 | u8 adjust_symbols:1; | 131 | u8 adjust_symbols:1; |
129 | u8 slen_calculated:1; | 132 | u8 slen_calculated:1; |
130 | u8 has_build_id:1; | 133 | u8 has_build_id:1; |
131 | enum dso_kernel_type kernel; | ||
132 | u8 hit:1; | 134 | u8 hit:1; |
133 | u8 annotate_warned:1; | 135 | u8 annotate_warned:1; |
136 | u8 sname_alloc:1; | ||
137 | u8 lname_alloc:1; | ||
134 | unsigned char origin; | 138 | unsigned char origin; |
135 | u8 sorted_by_name; | 139 | u8 sorted_by_name; |
136 | u8 loaded; | 140 | u8 loaded; |
@@ -146,6 +150,8 @@ struct dso *dso__new(const char *name); | |||
146 | struct dso *dso__new_kernel(const char *name); | 150 | struct dso *dso__new_kernel(const char *name); |
147 | void dso__delete(struct dso *self); | 151 | void dso__delete(struct dso *self); |
148 | 152 | ||
153 | int dso__name_len(const struct dso *self); | ||
154 | |||
149 | bool dso__loaded(const struct dso *self, enum map_type type); | 155 | bool dso__loaded(const struct dso *self, enum map_type type); |
150 | bool dso__sorted_by_name(const struct dso *self, enum map_type type); | 156 | bool dso__sorted_by_name(const struct dso *self, enum map_type type); |
151 | 157 | ||
@@ -207,13 +213,16 @@ int kallsyms__parse(const char *filename, void *arg, | |||
207 | int (*process_symbol)(void *arg, const char *name, | 213 | int (*process_symbol)(void *arg, const char *name, |
208 | char type, u64 start)); | 214 | char type, u64 start)); |
209 | 215 | ||
216 | void machine__destroy_kernel_maps(struct machine *self); | ||
210 | int __machine__create_kernel_maps(struct machine *self, struct dso *kernel); | 217 | int __machine__create_kernel_maps(struct machine *self, struct dso *kernel); |
211 | int machine__create_kernel_maps(struct machine *self); | 218 | int machine__create_kernel_maps(struct machine *self); |
212 | 219 | ||
213 | int machines__create_kernel_maps(struct rb_root *self, pid_t pid); | 220 | int machines__create_kernel_maps(struct rb_root *self, pid_t pid); |
214 | int machines__create_guest_kernel_maps(struct rb_root *self); | 221 | int machines__create_guest_kernel_maps(struct rb_root *self); |
222 | void machines__destroy_guest_kernel_maps(struct rb_root *self); | ||
215 | 223 | ||
216 | int symbol__init(void); | 224 | int symbol__init(void); |
225 | void symbol__exit(void); | ||
217 | bool symbol_type__is_a(char symbol_type, enum map_type map_type); | 226 | bool symbol_type__is_a(char symbol_type, enum map_type map_type); |
218 | 227 | ||
219 | size_t machine__fprintf_vmlinux_path(struct machine *self, FILE *fp); | 228 | size_t machine__fprintf_vmlinux_path(struct machine *self, FILE *fp); |
diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index 9a448b47400c..8c72d888e449 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c | |||
@@ -62,6 +62,13 @@ static struct thread *thread__new(pid_t pid) | |||
62 | return self; | 62 | return self; |
63 | } | 63 | } |
64 | 64 | ||
65 | void thread__delete(struct thread *self) | ||
66 | { | ||
67 | map_groups__exit(&self->mg); | ||
68 | free(self->comm); | ||
69 | free(self); | ||
70 | } | ||
71 | |||
65 | int thread__set_comm(struct thread *self, const char *comm) | 72 | int thread__set_comm(struct thread *self, const char *comm) |
66 | { | 73 | { |
67 | int err; | 74 | int err; |
diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h index ee6bbcf277ca..688500ff826f 100644 --- a/tools/perf/util/thread.h +++ b/tools/perf/util/thread.h | |||
@@ -20,6 +20,8 @@ struct thread { | |||
20 | 20 | ||
21 | struct perf_session; | 21 | struct perf_session; |
22 | 22 | ||
23 | void thread__delete(struct thread *self); | ||
24 | |||
23 | int find_all_tid(int pid, pid_t ** all_tid); | 25 | int find_all_tid(int pid, pid_t ** all_tid); |
24 | int thread__set_comm(struct thread *self, const char *comm); | 26 | int thread__set_comm(struct thread *self, const char *comm); |
25 | int thread__comm_len(struct thread *self); | 27 | int thread__comm_len(struct thread *self); |
diff --git a/tools/perf/util/ui/browser.c b/tools/perf/util/ui/browser.c new file mode 100644 index 000000000000..66f2d583d8c4 --- /dev/null +++ b/tools/perf/util/ui/browser.c | |||
@@ -0,0 +1,329 @@ | |||
1 | #define _GNU_SOURCE | ||
2 | #include <stdio.h> | ||
3 | #undef _GNU_SOURCE | ||
4 | /* | ||
5 | * slang versions <= 2.0.6 have a "#if HAVE_LONG_LONG" that breaks | ||
6 | * the build if it isn't defined. Use the equivalent one that glibc | ||
7 | * has on features.h. | ||
8 | */ | ||
9 | #include <features.h> | ||
10 | #ifndef HAVE_LONG_LONG | ||
11 | #define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG | ||
12 | #endif | ||
13 | #include <slang.h> | ||
14 | #include <linux/list.h> | ||
15 | #include <linux/rbtree.h> | ||
16 | #include <stdlib.h> | ||
17 | #include <sys/ttydefaults.h> | ||
18 | #include "browser.h" | ||
19 | #include "helpline.h" | ||
20 | #include "../color.h" | ||
21 | #include "../util.h" | ||
22 | |||
23 | #if SLANG_VERSION < 20104 | ||
24 | #define sltt_set_color(obj, name, fg, bg) \ | ||
25 | SLtt_set_color(obj,(char *)name, (char *)fg, (char *)bg) | ||
26 | #else | ||
27 | #define sltt_set_color SLtt_set_color | ||
28 | #endif | ||
29 | |||
30 | newtComponent newt_form__new(void); | ||
31 | |||
32 | int ui_browser__percent_color(double percent, bool current) | ||
33 | { | ||
34 | if (current) | ||
35 | return HE_COLORSET_SELECTED; | ||
36 | if (percent >= MIN_RED) | ||
37 | return HE_COLORSET_TOP; | ||
38 | if (percent >= MIN_GREEN) | ||
39 | return HE_COLORSET_MEDIUM; | ||
40 | return HE_COLORSET_NORMAL; | ||
41 | } | ||
42 | |||
43 | void ui_browser__list_head_seek(struct ui_browser *self, off_t offset, int whence) | ||
44 | { | ||
45 | struct list_head *head = self->entries; | ||
46 | struct list_head *pos; | ||
47 | |||
48 | switch (whence) { | ||
49 | case SEEK_SET: | ||
50 | pos = head->next; | ||
51 | break; | ||
52 | case SEEK_CUR: | ||
53 | pos = self->top; | ||
54 | break; | ||
55 | case SEEK_END: | ||
56 | pos = head->prev; | ||
57 | break; | ||
58 | default: | ||
59 | return; | ||
60 | } | ||
61 | |||
62 | if (offset > 0) { | ||
63 | while (offset-- != 0) | ||
64 | pos = pos->next; | ||
65 | } else { | ||
66 | while (offset++ != 0) | ||
67 | pos = pos->prev; | ||
68 | } | ||
69 | |||
70 | self->top = pos; | ||
71 | } | ||
72 | |||
73 | void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence) | ||
74 | { | ||
75 | struct rb_root *root = self->entries; | ||
76 | struct rb_node *nd; | ||
77 | |||
78 | switch (whence) { | ||
79 | case SEEK_SET: | ||
80 | nd = rb_first(root); | ||
81 | break; | ||
82 | case SEEK_CUR: | ||
83 | nd = self->top; | ||
84 | break; | ||
85 | case SEEK_END: | ||
86 | nd = rb_last(root); | ||
87 | break; | ||
88 | default: | ||
89 | return; | ||
90 | } | ||
91 | |||
92 | if (offset > 0) { | ||
93 | while (offset-- != 0) | ||
94 | nd = rb_next(nd); | ||
95 | } else { | ||
96 | while (offset++ != 0) | ||
97 | nd = rb_prev(nd); | ||
98 | } | ||
99 | |||
100 | self->top = nd; | ||
101 | } | ||
102 | |||
103 | unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self) | ||
104 | { | ||
105 | struct rb_node *nd; | ||
106 | int row = 0; | ||
107 | |||
108 | if (self->top == NULL) | ||
109 | self->top = rb_first(self->entries); | ||
110 | |||
111 | nd = self->top; | ||
112 | |||
113 | while (nd != NULL) { | ||
114 | SLsmg_gotorc(self->y + row, self->x); | ||
115 | self->write(self, nd, row); | ||
116 | if (++row == self->height) | ||
117 | break; | ||
118 | nd = rb_next(nd); | ||
119 | } | ||
120 | |||
121 | return row; | ||
122 | } | ||
123 | |||
124 | bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row) | ||
125 | { | ||
126 | return self->top_idx + row == self->index; | ||
127 | } | ||
128 | |||
129 | void ui_browser__refresh_dimensions(struct ui_browser *self) | ||
130 | { | ||
131 | int cols, rows; | ||
132 | newtGetScreenSize(&cols, &rows); | ||
133 | |||
134 | if (self->width > cols - 4) | ||
135 | self->width = cols - 4; | ||
136 | self->height = rows - 5; | ||
137 | if (self->height > self->nr_entries) | ||
138 | self->height = self->nr_entries; | ||
139 | self->y = (rows - self->height) / 2; | ||
140 | self->x = (cols - self->width) / 2; | ||
141 | } | ||
142 | |||
143 | void ui_browser__reset_index(struct ui_browser *self) | ||
144 | { | ||
145 | self->index = self->top_idx = 0; | ||
146 | self->seek(self, 0, SEEK_SET); | ||
147 | } | ||
148 | |||
149 | int ui_browser__show(struct ui_browser *self, const char *title, | ||
150 | const char *helpline, ...) | ||
151 | { | ||
152 | va_list ap; | ||
153 | |||
154 | if (self->form != NULL) { | ||
155 | newtFormDestroy(self->form); | ||
156 | newtPopWindow(); | ||
157 | } | ||
158 | ui_browser__refresh_dimensions(self); | ||
159 | newtCenteredWindow(self->width, self->height, title); | ||
160 | self->form = newt_form__new(); | ||
161 | if (self->form == NULL) | ||
162 | return -1; | ||
163 | |||
164 | self->sb = newtVerticalScrollbar(self->width, 0, self->height, | ||
165 | HE_COLORSET_NORMAL, | ||
166 | HE_COLORSET_SELECTED); | ||
167 | if (self->sb == NULL) | ||
168 | return -1; | ||
169 | |||
170 | newtFormAddHotKey(self->form, NEWT_KEY_UP); | ||
171 | newtFormAddHotKey(self->form, NEWT_KEY_DOWN); | ||
172 | newtFormAddHotKey(self->form, NEWT_KEY_PGUP); | ||
173 | newtFormAddHotKey(self->form, NEWT_KEY_PGDN); | ||
174 | newtFormAddHotKey(self->form, NEWT_KEY_HOME); | ||
175 | newtFormAddHotKey(self->form, NEWT_KEY_END); | ||
176 | newtFormAddHotKey(self->form, ' '); | ||
177 | newtFormAddComponent(self->form, self->sb); | ||
178 | |||
179 | va_start(ap, helpline); | ||
180 | ui_helpline__vpush(helpline, ap); | ||
181 | va_end(ap); | ||
182 | return 0; | ||
183 | } | ||
184 | |||
185 | void ui_browser__hide(struct ui_browser *self) | ||
186 | { | ||
187 | newtFormDestroy(self->form); | ||
188 | newtPopWindow(); | ||
189 | self->form = NULL; | ||
190 | ui_helpline__pop(); | ||
191 | } | ||
192 | |||
193 | int ui_browser__refresh(struct ui_browser *self) | ||
194 | { | ||
195 | int row; | ||
196 | |||
197 | newtScrollbarSet(self->sb, self->index, self->nr_entries - 1); | ||
198 | row = self->refresh(self); | ||
199 | SLsmg_set_color(HE_COLORSET_NORMAL); | ||
200 | SLsmg_fill_region(self->y + row, self->x, | ||
201 | self->height - row, self->width, ' '); | ||
202 | |||
203 | return 0; | ||
204 | } | ||
205 | |||
206 | int ui_browser__run(struct ui_browser *self, struct newtExitStruct *es) | ||
207 | { | ||
208 | if (ui_browser__refresh(self) < 0) | ||
209 | return -1; | ||
210 | |||
211 | while (1) { | ||
212 | off_t offset; | ||
213 | |||
214 | newtFormRun(self->form, es); | ||
215 | |||
216 | if (es->reason != NEWT_EXIT_HOTKEY) | ||
217 | break; | ||
218 | if (is_exit_key(es->u.key)) | ||
219 | return es->u.key; | ||
220 | switch (es->u.key) { | ||
221 | case NEWT_KEY_DOWN: | ||
222 | if (self->index == self->nr_entries - 1) | ||
223 | break; | ||
224 | ++self->index; | ||
225 | if (self->index == self->top_idx + self->height) { | ||
226 | ++self->top_idx; | ||
227 | self->seek(self, +1, SEEK_CUR); | ||
228 | } | ||
229 | break; | ||
230 | case NEWT_KEY_UP: | ||
231 | if (self->index == 0) | ||
232 | break; | ||
233 | --self->index; | ||
234 | if (self->index < self->top_idx) { | ||
235 | --self->top_idx; | ||
236 | self->seek(self, -1, SEEK_CUR); | ||
237 | } | ||
238 | break; | ||
239 | case NEWT_KEY_PGDN: | ||
240 | case ' ': | ||
241 | if (self->top_idx + self->height > self->nr_entries - 1) | ||
242 | break; | ||
243 | |||
244 | offset = self->height; | ||
245 | if (self->index + offset > self->nr_entries - 1) | ||
246 | offset = self->nr_entries - 1 - self->index; | ||
247 | self->index += offset; | ||
248 | self->top_idx += offset; | ||
249 | self->seek(self, +offset, SEEK_CUR); | ||
250 | break; | ||
251 | case NEWT_KEY_PGUP: | ||
252 | if (self->top_idx == 0) | ||
253 | break; | ||
254 | |||
255 | if (self->top_idx < self->height) | ||
256 | offset = self->top_idx; | ||
257 | else | ||
258 | offset = self->height; | ||
259 | |||
260 | self->index -= offset; | ||
261 | self->top_idx -= offset; | ||
262 | self->seek(self, -offset, SEEK_CUR); | ||
263 | break; | ||
264 | case NEWT_KEY_HOME: | ||
265 | ui_browser__reset_index(self); | ||
266 | break; | ||
267 | case NEWT_KEY_END: | ||
268 | offset = self->height - 1; | ||
269 | if (offset >= self->nr_entries) | ||
270 | offset = self->nr_entries - 1; | ||
271 | |||
272 | self->index = self->nr_entries - 1; | ||
273 | self->top_idx = self->index - offset; | ||
274 | self->seek(self, -offset, SEEK_END); | ||
275 | break; | ||
276 | default: | ||
277 | return es->u.key; | ||
278 | } | ||
279 | if (ui_browser__refresh(self) < 0) | ||
280 | return -1; | ||
281 | } | ||
282 | return 0; | ||
283 | } | ||
284 | |||
285 | unsigned int ui_browser__list_head_refresh(struct ui_browser *self) | ||
286 | { | ||
287 | struct list_head *pos; | ||
288 | struct list_head *head = self->entries; | ||
289 | int row = 0; | ||
290 | |||
291 | if (self->top == NULL || self->top == self->entries) | ||
292 | self->top = head->next; | ||
293 | |||
294 | pos = self->top; | ||
295 | |||
296 | list_for_each_from(pos, head) { | ||
297 | SLsmg_gotorc(self->y + row, self->x); | ||
298 | self->write(self, pos, row); | ||
299 | if (++row == self->height) | ||
300 | break; | ||
301 | } | ||
302 | |||
303 | return row; | ||
304 | } | ||
305 | |||
306 | static struct newtPercentTreeColors { | ||
307 | const char *topColorFg, *topColorBg; | ||
308 | const char *mediumColorFg, *mediumColorBg; | ||
309 | const char *normalColorFg, *normalColorBg; | ||
310 | const char *selColorFg, *selColorBg; | ||
311 | const char *codeColorFg, *codeColorBg; | ||
312 | } defaultPercentTreeColors = { | ||
313 | "red", "lightgray", | ||
314 | "green", "lightgray", | ||
315 | "black", "lightgray", | ||
316 | "lightgray", "magenta", | ||
317 | "blue", "lightgray", | ||
318 | }; | ||
319 | |||
320 | void ui_browser__init(void) | ||
321 | { | ||
322 | struct newtPercentTreeColors *c = &defaultPercentTreeColors; | ||
323 | |||
324 | sltt_set_color(HE_COLORSET_TOP, NULL, c->topColorFg, c->topColorBg); | ||
325 | sltt_set_color(HE_COLORSET_MEDIUM, NULL, c->mediumColorFg, c->mediumColorBg); | ||
326 | sltt_set_color(HE_COLORSET_NORMAL, NULL, c->normalColorFg, c->normalColorBg); | ||
327 | sltt_set_color(HE_COLORSET_SELECTED, NULL, c->selColorFg, c->selColorBg); | ||
328 | sltt_set_color(HE_COLORSET_CODE, NULL, c->codeColorFg, c->codeColorBg); | ||
329 | } | ||
diff --git a/tools/perf/util/ui/browser.h b/tools/perf/util/ui/browser.h new file mode 100644 index 000000000000..0b9f829214f7 --- /dev/null +++ b/tools/perf/util/ui/browser.h | |||
@@ -0,0 +1,46 @@ | |||
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 | |||
28 | int ui_browser__percent_color(double percent, bool current); | ||
29 | bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row); | ||
30 | void ui_browser__refresh_dimensions(struct ui_browser *self); | ||
31 | void ui_browser__reset_index(struct ui_browser *self); | ||
32 | |||
33 | int ui_browser__show(struct ui_browser *self, const char *title, | ||
34 | const char *helpline, ...); | ||
35 | void ui_browser__hide(struct ui_browser *self); | ||
36 | int ui_browser__refresh(struct ui_browser *self); | ||
37 | int ui_browser__run(struct ui_browser *self, struct newtExitStruct *es); | ||
38 | |||
39 | void ui_browser__rb_tree_seek(struct ui_browser *self, off_t offset, int whence); | ||
40 | unsigned int ui_browser__rb_tree_refresh(struct ui_browser *self); | ||
41 | |||
42 | void ui_browser__list_head_seek(struct ui_browser *self, off_t offset, int whence); | ||
43 | unsigned int ui_browser__list_head_refresh(struct ui_browser *self); | ||
44 | |||
45 | void ui_browser__init(void); | ||
46 | #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 000000000000..55ff792459ac --- /dev/null +++ b/tools/perf/util/ui/browsers/annotate.c | |||
@@ -0,0 +1,240 @@ | |||
1 | #include "../browser.h" | ||
2 | #include "../helpline.h" | ||
3 | #include "../libslang.h" | ||
4 | #include "../../hist.h" | ||
5 | #include "../../sort.h" | ||
6 | #include "../../symbol.h" | ||
7 | |||
8 | static void ui__error_window(const char *fmt, ...) | ||
9 | { | ||
10 | va_list ap; | ||
11 | |||
12 | va_start(ap, fmt); | ||
13 | newtWinMessagev((char *)"Error", (char *)"Ok", (char *)fmt, ap); | ||
14 | va_end(ap); | ||
15 | } | ||
16 | |||
17 | struct annotate_browser { | ||
18 | struct ui_browser b; | ||
19 | struct rb_root entries; | ||
20 | struct rb_node *curr_hot; | ||
21 | }; | ||
22 | |||
23 | struct objdump_line_rb_node { | ||
24 | struct rb_node rb_node; | ||
25 | double percent; | ||
26 | u32 idx; | ||
27 | }; | ||
28 | |||
29 | static inline | ||
30 | struct objdump_line_rb_node *objdump_line__rb(struct objdump_line *self) | ||
31 | { | ||
32 | return (struct objdump_line_rb_node *)(self + 1); | ||
33 | } | ||
34 | |||
35 | static void annotate_browser__write(struct ui_browser *self, void *entry, int row) | ||
36 | { | ||
37 | struct objdump_line *ol = rb_entry(entry, struct objdump_line, node); | ||
38 | bool current_entry = ui_browser__is_current_entry(self, row); | ||
39 | int width = self->width; | ||
40 | |||
41 | if (ol->offset != -1) { | ||
42 | struct objdump_line_rb_node *olrb = objdump_line__rb(ol); | ||
43 | int color = ui_browser__percent_color(olrb->percent, current_entry); | ||
44 | SLsmg_set_color(color); | ||
45 | slsmg_printf(" %7.2f ", olrb->percent); | ||
46 | if (!current_entry) | ||
47 | SLsmg_set_color(HE_COLORSET_CODE); | ||
48 | } else { | ||
49 | int color = ui_browser__percent_color(0, current_entry); | ||
50 | SLsmg_set_color(color); | ||
51 | slsmg_write_nstring(" ", 9); | ||
52 | } | ||
53 | |||
54 | SLsmg_write_char(':'); | ||
55 | slsmg_write_nstring(" ", 8); | ||
56 | if (!*ol->line) | ||
57 | slsmg_write_nstring(" ", width - 18); | ||
58 | else | ||
59 | slsmg_write_nstring(ol->line, width - 18); | ||
60 | } | ||
61 | |||
62 | static double objdump_line__calc_percent(struct objdump_line *self, | ||
63 | struct list_head *head, | ||
64 | struct symbol *sym) | ||
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 sym_priv *priv = symbol__priv(sym); | ||
72 | struct sym_ext *sym_ext = priv->ext; | ||
73 | struct sym_hist *h = priv->hist; | ||
74 | s64 offset = self->offset; | ||
75 | struct objdump_line *next = objdump__get_next_ip_line(head, self); | ||
76 | |||
77 | |||
78 | while (offset < (s64)len && | ||
79 | (next == NULL || offset < next->offset)) { | ||
80 | if (sym_ext) { | ||
81 | percent += sym_ext[offset].percent; | ||
82 | } else | ||
83 | hits += h->ip[offset]; | ||
84 | |||
85 | ++offset; | ||
86 | } | ||
87 | |||
88 | if (sym_ext == NULL && h->sum) | ||
89 | percent = 100.0 * hits / h->sum; | ||
90 | } | ||
91 | |||
92 | return percent; | ||
93 | } | ||
94 | |||
95 | static void objdump__insert_line(struct rb_root *self, | ||
96 | struct objdump_line_rb_node *line) | ||
97 | { | ||
98 | struct rb_node **p = &self->rb_node; | ||
99 | struct rb_node *parent = NULL; | ||
100 | struct objdump_line_rb_node *l; | ||
101 | |||
102 | while (*p != NULL) { | ||
103 | parent = *p; | ||
104 | l = rb_entry(parent, struct objdump_line_rb_node, rb_node); | ||
105 | if (line->percent < l->percent) | ||
106 | p = &(*p)->rb_left; | ||
107 | else | ||
108 | p = &(*p)->rb_right; | ||
109 | } | ||
110 | rb_link_node(&line->rb_node, parent, p); | ||
111 | rb_insert_color(&line->rb_node, self); | ||
112 | } | ||
113 | |||
114 | static void annotate_browser__set_top(struct annotate_browser *self, | ||
115 | struct rb_node *nd) | ||
116 | { | ||
117 | struct objdump_line_rb_node *rbpos; | ||
118 | struct objdump_line *pos; | ||
119 | unsigned back; | ||
120 | |||
121 | ui_browser__refresh_dimensions(&self->b); | ||
122 | back = self->b.height / 2; | ||
123 | rbpos = rb_entry(nd, struct objdump_line_rb_node, rb_node); | ||
124 | pos = ((struct objdump_line *)rbpos) - 1; | ||
125 | self->b.top_idx = self->b.index = rbpos->idx; | ||
126 | |||
127 | while (self->b.top_idx != 0 && back != 0) { | ||
128 | pos = list_entry(pos->node.prev, struct objdump_line, node); | ||
129 | |||
130 | --self->b.top_idx; | ||
131 | --back; | ||
132 | } | ||
133 | |||
134 | self->b.top = pos; | ||
135 | self->curr_hot = nd; | ||
136 | } | ||
137 | |||
138 | static int annotate_browser__run(struct annotate_browser *self, | ||
139 | struct newtExitStruct *es) | ||
140 | { | ||
141 | struct rb_node *nd; | ||
142 | struct hist_entry *he = self->b.priv; | ||
143 | |||
144 | if (ui_browser__show(&self->b, he->ms.sym->name, | ||
145 | "<- or ESC: exit, TAB/shift+TAB: cycle thru samples") < 0) | ||
146 | return -1; | ||
147 | |||
148 | newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); | ||
149 | |||
150 | nd = self->curr_hot; | ||
151 | if (nd) { | ||
152 | newtFormAddHotKey(self->b.form, NEWT_KEY_TAB); | ||
153 | newtFormAddHotKey(self->b.form, NEWT_KEY_UNTAB); | ||
154 | } | ||
155 | |||
156 | while (1) { | ||
157 | ui_browser__run(&self->b, es); | ||
158 | |||
159 | if (es->reason != NEWT_EXIT_HOTKEY) | ||
160 | break; | ||
161 | |||
162 | switch (es->u.key) { | ||
163 | case NEWT_KEY_TAB: | ||
164 | nd = rb_prev(nd); | ||
165 | if (nd == NULL) | ||
166 | nd = rb_last(&self->entries); | ||
167 | annotate_browser__set_top(self, nd); | ||
168 | break; | ||
169 | case NEWT_KEY_UNTAB: | ||
170 | nd = rb_next(nd); | ||
171 | if (nd == NULL) | ||
172 | nd = rb_first(&self->entries); | ||
173 | annotate_browser__set_top(self, nd); | ||
174 | break; | ||
175 | default: | ||
176 | goto out; | ||
177 | } | ||
178 | } | ||
179 | out: | ||
180 | ui_browser__hide(&self->b); | ||
181 | return 0; | ||
182 | } | ||
183 | |||
184 | int hist_entry__tui_annotate(struct hist_entry *self) | ||
185 | { | ||
186 | struct newtExitStruct es; | ||
187 | struct objdump_line *pos, *n; | ||
188 | struct objdump_line_rb_node *rbpos; | ||
189 | LIST_HEAD(head); | ||
190 | struct annotate_browser browser = { | ||
191 | .b = { | ||
192 | .entries = &head, | ||
193 | .refresh = ui_browser__list_head_refresh, | ||
194 | .seek = ui_browser__list_head_seek, | ||
195 | .write = annotate_browser__write, | ||
196 | .priv = self, | ||
197 | }, | ||
198 | }; | ||
199 | int ret; | ||
200 | |||
201 | if (self->ms.sym == NULL) | ||
202 | return -1; | ||
203 | |||
204 | if (self->ms.map->dso->annotate_warned) | ||
205 | return -1; | ||
206 | |||
207 | if (hist_entry__annotate(self, &head, sizeof(*rbpos)) < 0) { | ||
208 | ui__error_window(ui_helpline__last_msg); | ||
209 | return -1; | ||
210 | } | ||
211 | |||
212 | ui_helpline__push("Press <- or ESC to exit"); | ||
213 | |||
214 | list_for_each_entry(pos, &head, node) { | ||
215 | size_t line_len = strlen(pos->line); | ||
216 | if (browser.b.width < line_len) | ||
217 | browser.b.width = line_len; | ||
218 | rbpos = objdump_line__rb(pos); | ||
219 | rbpos->idx = browser.b.nr_entries++; | ||
220 | rbpos->percent = objdump_line__calc_percent(pos, &head, self->ms.sym); | ||
221 | if (rbpos->percent < 0.01) | ||
222 | continue; | ||
223 | objdump__insert_line(&browser.entries, rbpos); | ||
224 | } | ||
225 | |||
226 | /* | ||
227 | * Position the browser at the hottest line. | ||
228 | */ | ||
229 | browser.curr_hot = rb_last(&browser.entries); | ||
230 | if (browser.curr_hot) | ||
231 | annotate_browser__set_top(&browser, browser.curr_hot); | ||
232 | |||
233 | browser.b.width += 18; /* Percentage */ | ||
234 | ret = annotate_browser__run(&browser, &es); | ||
235 | list_for_each_entry_safe(pos, n, &head, node) { | ||
236 | list_del(&pos->node); | ||
237 | objdump_line__free(pos); | ||
238 | } | ||
239 | return ret; | ||
240 | } | ||
diff --git a/tools/perf/util/ui/browsers/hists.c b/tools/perf/util/ui/browsers/hists.c new file mode 100644 index 000000000000..dafdf6775d77 --- /dev/null +++ b/tools/perf/util/ui/browsers/hists.c | |||
@@ -0,0 +1,948 @@ | |||
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 "../../hist.h" | ||
11 | #include "../../pstack.h" | ||
12 | #include "../../sort.h" | ||
13 | #include "../../util.h" | ||
14 | |||
15 | #include "../browser.h" | ||
16 | #include "../helpline.h" | ||
17 | #include "../util.h" | ||
18 | #include "map.h" | ||
19 | |||
20 | struct hist_browser { | ||
21 | struct ui_browser b; | ||
22 | struct hists *hists; | ||
23 | struct hist_entry *he_selection; | ||
24 | struct map_symbol *selection; | ||
25 | }; | ||
26 | |||
27 | static void hist_browser__refresh_dimensions(struct hist_browser *self) | ||
28 | { | ||
29 | /* 3 == +/- toggle symbol before actual hist_entry rendering */ | ||
30 | self->b.width = 3 + (hists__sort_list_width(self->hists) + | ||
31 | sizeof("[k]")); | ||
32 | } | ||
33 | |||
34 | static void hist_browser__reset(struct hist_browser *self) | ||
35 | { | ||
36 | self->b.nr_entries = self->hists->nr_entries; | ||
37 | hist_browser__refresh_dimensions(self); | ||
38 | ui_browser__reset_index(&self->b); | ||
39 | } | ||
40 | |||
41 | static char tree__folded_sign(bool unfolded) | ||
42 | { | ||
43 | return unfolded ? '-' : '+'; | ||
44 | } | ||
45 | |||
46 | static char map_symbol__folded(const struct map_symbol *self) | ||
47 | { | ||
48 | return self->has_children ? tree__folded_sign(self->unfolded) : ' '; | ||
49 | } | ||
50 | |||
51 | static char hist_entry__folded(const struct hist_entry *self) | ||
52 | { | ||
53 | return map_symbol__folded(&self->ms); | ||
54 | } | ||
55 | |||
56 | static char callchain_list__folded(const struct callchain_list *self) | ||
57 | { | ||
58 | return map_symbol__folded(&self->ms); | ||
59 | } | ||
60 | |||
61 | static int callchain_node__count_rows_rb_tree(struct callchain_node *self) | ||
62 | { | ||
63 | int n = 0; | ||
64 | struct rb_node *nd; | ||
65 | |||
66 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | ||
67 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | ||
68 | struct callchain_list *chain; | ||
69 | char folded_sign = ' '; /* No children */ | ||
70 | |||
71 | list_for_each_entry(chain, &child->val, list) { | ||
72 | ++n; | ||
73 | /* We need this because we may not have children */ | ||
74 | folded_sign = callchain_list__folded(chain); | ||
75 | if (folded_sign == '+') | ||
76 | break; | ||
77 | } | ||
78 | |||
79 | if (folded_sign == '-') /* Have children and they're unfolded */ | ||
80 | n += callchain_node__count_rows_rb_tree(child); | ||
81 | } | ||
82 | |||
83 | return n; | ||
84 | } | ||
85 | |||
86 | static int callchain_node__count_rows(struct callchain_node *node) | ||
87 | { | ||
88 | struct callchain_list *chain; | ||
89 | bool unfolded = false; | ||
90 | int n = 0; | ||
91 | |||
92 | list_for_each_entry(chain, &node->val, list) { | ||
93 | ++n; | ||
94 | unfolded = chain->ms.unfolded; | ||
95 | } | ||
96 | |||
97 | if (unfolded) | ||
98 | n += callchain_node__count_rows_rb_tree(node); | ||
99 | |||
100 | return n; | ||
101 | } | ||
102 | |||
103 | static int callchain__count_rows(struct rb_root *chain) | ||
104 | { | ||
105 | struct rb_node *nd; | ||
106 | int n = 0; | ||
107 | |||
108 | for (nd = rb_first(chain); nd; nd = rb_next(nd)) { | ||
109 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
110 | n += callchain_node__count_rows(node); | ||
111 | } | ||
112 | |||
113 | return n; | ||
114 | } | ||
115 | |||
116 | static bool map_symbol__toggle_fold(struct map_symbol *self) | ||
117 | { | ||
118 | if (!self->has_children) | ||
119 | return false; | ||
120 | |||
121 | self->unfolded = !self->unfolded; | ||
122 | return true; | ||
123 | } | ||
124 | |||
125 | static void callchain_node__init_have_children_rb_tree(struct callchain_node *self) | ||
126 | { | ||
127 | struct rb_node *nd = rb_first(&self->rb_root); | ||
128 | |||
129 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | ||
130 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | ||
131 | struct callchain_list *chain; | ||
132 | int first = true; | ||
133 | |||
134 | list_for_each_entry(chain, &child->val, list) { | ||
135 | if (first) { | ||
136 | first = false; | ||
137 | chain->ms.has_children = chain->list.next != &child->val || | ||
138 | rb_first(&child->rb_root) != NULL; | ||
139 | } else | ||
140 | chain->ms.has_children = chain->list.next == &child->val && | ||
141 | rb_first(&child->rb_root) != NULL; | ||
142 | } | ||
143 | |||
144 | callchain_node__init_have_children_rb_tree(child); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | static void callchain_node__init_have_children(struct callchain_node *self) | ||
149 | { | ||
150 | struct callchain_list *chain; | ||
151 | |||
152 | list_for_each_entry(chain, &self->val, list) | ||
153 | chain->ms.has_children = rb_first(&self->rb_root) != NULL; | ||
154 | |||
155 | callchain_node__init_have_children_rb_tree(self); | ||
156 | } | ||
157 | |||
158 | static void callchain__init_have_children(struct rb_root *self) | ||
159 | { | ||
160 | struct rb_node *nd; | ||
161 | |||
162 | for (nd = rb_first(self); nd; nd = rb_next(nd)) { | ||
163 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
164 | callchain_node__init_have_children(node); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | static void hist_entry__init_have_children(struct hist_entry *self) | ||
169 | { | ||
170 | if (!self->init_have_children) { | ||
171 | callchain__init_have_children(&self->sorted_chain); | ||
172 | self->init_have_children = true; | ||
173 | } | ||
174 | } | ||
175 | |||
176 | static bool hist_browser__toggle_fold(struct hist_browser *self) | ||
177 | { | ||
178 | if (map_symbol__toggle_fold(self->selection)) { | ||
179 | struct hist_entry *he = self->he_selection; | ||
180 | |||
181 | hist_entry__init_have_children(he); | ||
182 | self->hists->nr_entries -= he->nr_rows; | ||
183 | |||
184 | if (he->ms.unfolded) | ||
185 | he->nr_rows = callchain__count_rows(&he->sorted_chain); | ||
186 | else | ||
187 | he->nr_rows = 0; | ||
188 | self->hists->nr_entries += he->nr_rows; | ||
189 | self->b.nr_entries = self->hists->nr_entries; | ||
190 | |||
191 | return true; | ||
192 | } | ||
193 | |||
194 | /* If it doesn't have children, no toggling performed */ | ||
195 | return false; | ||
196 | } | ||
197 | |||
198 | static int hist_browser__run(struct hist_browser *self, const char *title, | ||
199 | struct newtExitStruct *es) | ||
200 | { | ||
201 | char str[256], unit; | ||
202 | unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE]; | ||
203 | |||
204 | self->b.entries = &self->hists->entries; | ||
205 | self->b.nr_entries = self->hists->nr_entries; | ||
206 | |||
207 | hist_browser__refresh_dimensions(self); | ||
208 | |||
209 | nr_events = convert_unit(nr_events, &unit); | ||
210 | snprintf(str, sizeof(str), "Events: %lu%c ", | ||
211 | nr_events, unit); | ||
212 | newtDrawRootText(0, 0, str); | ||
213 | |||
214 | if (ui_browser__show(&self->b, title, | ||
215 | "Press '?' for help on key bindings") < 0) | ||
216 | return -1; | ||
217 | |||
218 | newtFormAddHotKey(self->b.form, 'a'); | ||
219 | newtFormAddHotKey(self->b.form, '?'); | ||
220 | newtFormAddHotKey(self->b.form, 'h'); | ||
221 | newtFormAddHotKey(self->b.form, 'd'); | ||
222 | newtFormAddHotKey(self->b.form, 'D'); | ||
223 | newtFormAddHotKey(self->b.form, 't'); | ||
224 | |||
225 | newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); | ||
226 | newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT); | ||
227 | newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER); | ||
228 | |||
229 | while (1) { | ||
230 | ui_browser__run(&self->b, es); | ||
231 | |||
232 | if (es->reason != NEWT_EXIT_HOTKEY) | ||
233 | break; | ||
234 | switch (es->u.key) { | ||
235 | case 'D': { /* Debug */ | ||
236 | static int seq; | ||
237 | struct hist_entry *h = rb_entry(self->b.top, | ||
238 | struct hist_entry, rb_node); | ||
239 | ui_helpline__pop(); | ||
240 | ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", | ||
241 | seq++, self->b.nr_entries, | ||
242 | self->hists->nr_entries, | ||
243 | self->b.height, | ||
244 | self->b.index, | ||
245 | self->b.top_idx, | ||
246 | h->row_offset, h->nr_rows); | ||
247 | } | ||
248 | continue; | ||
249 | case NEWT_KEY_ENTER: | ||
250 | if (hist_browser__toggle_fold(self)) | ||
251 | break; | ||
252 | /* fall thru */ | ||
253 | default: | ||
254 | return 0; | ||
255 | } | ||
256 | } | ||
257 | |||
258 | ui_browser__hide(&self->b); | ||
259 | return 0; | ||
260 | } | ||
261 | |||
262 | static char *callchain_list__sym_name(struct callchain_list *self, | ||
263 | char *bf, size_t bfsize) | ||
264 | { | ||
265 | if (self->ms.sym) | ||
266 | return self->ms.sym->name; | ||
267 | |||
268 | snprintf(bf, bfsize, "%#Lx", self->ip); | ||
269 | return bf; | ||
270 | } | ||
271 | |||
272 | #define LEVEL_OFFSET_STEP 3 | ||
273 | |||
274 | static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self, | ||
275 | struct callchain_node *chain_node, | ||
276 | u64 total, int level, | ||
277 | unsigned short row, | ||
278 | off_t *row_offset, | ||
279 | bool *is_current_entry) | ||
280 | { | ||
281 | struct rb_node *node; | ||
282 | int first_row = row, width, offset = level * LEVEL_OFFSET_STEP; | ||
283 | u64 new_total, remaining; | ||
284 | |||
285 | if (callchain_param.mode == CHAIN_GRAPH_REL) | ||
286 | new_total = chain_node->children_hit; | ||
287 | else | ||
288 | new_total = total; | ||
289 | |||
290 | remaining = new_total; | ||
291 | node = rb_first(&chain_node->rb_root); | ||
292 | while (node) { | ||
293 | struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); | ||
294 | struct rb_node *next = rb_next(node); | ||
295 | u64 cumul = cumul_hits(child); | ||
296 | struct callchain_list *chain; | ||
297 | char folded_sign = ' '; | ||
298 | int first = true; | ||
299 | int extra_offset = 0; | ||
300 | |||
301 | remaining -= cumul; | ||
302 | |||
303 | list_for_each_entry(chain, &child->val, list) { | ||
304 | char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str; | ||
305 | const char *str; | ||
306 | int color; | ||
307 | bool was_first = first; | ||
308 | |||
309 | if (first) { | ||
310 | first = false; | ||
311 | chain->ms.has_children = chain->list.next != &child->val || | ||
312 | rb_first(&child->rb_root) != NULL; | ||
313 | } else { | ||
314 | extra_offset = LEVEL_OFFSET_STEP; | ||
315 | chain->ms.has_children = chain->list.next == &child->val && | ||
316 | rb_first(&child->rb_root) != NULL; | ||
317 | } | ||
318 | |||
319 | folded_sign = callchain_list__folded(chain); | ||
320 | if (*row_offset != 0) { | ||
321 | --*row_offset; | ||
322 | goto do_next; | ||
323 | } | ||
324 | |||
325 | alloc_str = NULL; | ||
326 | str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | ||
327 | if (was_first) { | ||
328 | double percent = cumul * 100.0 / new_total; | ||
329 | |||
330 | if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) | ||
331 | str = "Not enough memory!"; | ||
332 | else | ||
333 | str = alloc_str; | ||
334 | } | ||
335 | |||
336 | color = HE_COLORSET_NORMAL; | ||
337 | width = self->b.width - (offset + extra_offset + 2); | ||
338 | if (ui_browser__is_current_entry(&self->b, row)) { | ||
339 | self->selection = &chain->ms; | ||
340 | color = HE_COLORSET_SELECTED; | ||
341 | *is_current_entry = true; | ||
342 | } | ||
343 | |||
344 | SLsmg_set_color(color); | ||
345 | SLsmg_gotorc(self->b.y + row, self->b.x); | ||
346 | slsmg_write_nstring(" ", offset + extra_offset); | ||
347 | slsmg_printf("%c ", folded_sign); | ||
348 | slsmg_write_nstring(str, width); | ||
349 | free(alloc_str); | ||
350 | |||
351 | if (++row == self->b.height) | ||
352 | goto out; | ||
353 | do_next: | ||
354 | if (folded_sign == '+') | ||
355 | break; | ||
356 | } | ||
357 | |||
358 | if (folded_sign == '-') { | ||
359 | const int new_level = level + (extra_offset ? 2 : 1); | ||
360 | row += hist_browser__show_callchain_node_rb_tree(self, child, new_total, | ||
361 | new_level, row, row_offset, | ||
362 | is_current_entry); | ||
363 | } | ||
364 | if (row == self->b.height) | ||
365 | goto out; | ||
366 | node = next; | ||
367 | } | ||
368 | out: | ||
369 | return row - first_row; | ||
370 | } | ||
371 | |||
372 | static int hist_browser__show_callchain_node(struct hist_browser *self, | ||
373 | struct callchain_node *node, | ||
374 | int level, unsigned short row, | ||
375 | off_t *row_offset, | ||
376 | bool *is_current_entry) | ||
377 | { | ||
378 | struct callchain_list *chain; | ||
379 | int first_row = row, | ||
380 | offset = level * LEVEL_OFFSET_STEP, | ||
381 | width = self->b.width - offset; | ||
382 | char folded_sign = ' '; | ||
383 | |||
384 | list_for_each_entry(chain, &node->val, list) { | ||
385 | char ipstr[BITS_PER_LONG / 4 + 1], *s; | ||
386 | int color; | ||
387 | /* | ||
388 | * FIXME: This should be moved to somewhere else, | ||
389 | * probably when the callchain is created, so as not to | ||
390 | * traverse it all over again | ||
391 | */ | ||
392 | chain->ms.has_children = rb_first(&node->rb_root) != NULL; | ||
393 | folded_sign = callchain_list__folded(chain); | ||
394 | |||
395 | if (*row_offset != 0) { | ||
396 | --*row_offset; | ||
397 | continue; | ||
398 | } | ||
399 | |||
400 | color = HE_COLORSET_NORMAL; | ||
401 | if (ui_browser__is_current_entry(&self->b, row)) { | ||
402 | self->selection = &chain->ms; | ||
403 | color = HE_COLORSET_SELECTED; | ||
404 | *is_current_entry = true; | ||
405 | } | ||
406 | |||
407 | s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | ||
408 | SLsmg_gotorc(self->b.y + row, self->b.x); | ||
409 | SLsmg_set_color(color); | ||
410 | slsmg_write_nstring(" ", offset); | ||
411 | slsmg_printf("%c ", folded_sign); | ||
412 | slsmg_write_nstring(s, width - 2); | ||
413 | |||
414 | if (++row == self->b.height) | ||
415 | goto out; | ||
416 | } | ||
417 | |||
418 | if (folded_sign == '-') | ||
419 | row += hist_browser__show_callchain_node_rb_tree(self, node, | ||
420 | self->hists->stats.total_period, | ||
421 | level + 1, row, | ||
422 | row_offset, | ||
423 | is_current_entry); | ||
424 | out: | ||
425 | return row - first_row; | ||
426 | } | ||
427 | |||
428 | static int hist_browser__show_callchain(struct hist_browser *self, | ||
429 | struct rb_root *chain, | ||
430 | int level, unsigned short row, | ||
431 | off_t *row_offset, | ||
432 | bool *is_current_entry) | ||
433 | { | ||
434 | struct rb_node *nd; | ||
435 | int first_row = row; | ||
436 | |||
437 | for (nd = rb_first(chain); nd; nd = rb_next(nd)) { | ||
438 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | ||
439 | |||
440 | row += hist_browser__show_callchain_node(self, node, level, | ||
441 | row, row_offset, | ||
442 | is_current_entry); | ||
443 | if (row == self->b.height) | ||
444 | break; | ||
445 | } | ||
446 | |||
447 | return row - first_row; | ||
448 | } | ||
449 | |||
450 | static int hist_browser__show_entry(struct hist_browser *self, | ||
451 | struct hist_entry *entry, | ||
452 | unsigned short row) | ||
453 | { | ||
454 | char s[256]; | ||
455 | double percent; | ||
456 | int printed = 0; | ||
457 | int color, width = self->b.width; | ||
458 | char folded_sign = ' '; | ||
459 | bool current_entry = ui_browser__is_current_entry(&self->b, row); | ||
460 | off_t row_offset = entry->row_offset; | ||
461 | |||
462 | if (current_entry) { | ||
463 | self->he_selection = entry; | ||
464 | self->selection = &entry->ms; | ||
465 | } | ||
466 | |||
467 | if (symbol_conf.use_callchain) { | ||
468 | entry->ms.has_children = !RB_EMPTY_ROOT(&entry->sorted_chain); | ||
469 | folded_sign = hist_entry__folded(entry); | ||
470 | } | ||
471 | |||
472 | if (row_offset == 0) { | ||
473 | hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false, | ||
474 | 0, false, self->hists->stats.total_period); | ||
475 | percent = (entry->period * 100.0) / self->hists->stats.total_period; | ||
476 | |||
477 | color = HE_COLORSET_SELECTED; | ||
478 | if (!current_entry) { | ||
479 | if (percent >= MIN_RED) | ||
480 | color = HE_COLORSET_TOP; | ||
481 | else if (percent >= MIN_GREEN) | ||
482 | color = HE_COLORSET_MEDIUM; | ||
483 | else | ||
484 | color = HE_COLORSET_NORMAL; | ||
485 | } | ||
486 | |||
487 | SLsmg_set_color(color); | ||
488 | SLsmg_gotorc(self->b.y + row, self->b.x); | ||
489 | if (symbol_conf.use_callchain) { | ||
490 | slsmg_printf("%c ", folded_sign); | ||
491 | width -= 2; | ||
492 | } | ||
493 | slsmg_write_nstring(s, width); | ||
494 | ++row; | ||
495 | ++printed; | ||
496 | } else | ||
497 | --row_offset; | ||
498 | |||
499 | if (folded_sign == '-' && row != self->b.height) { | ||
500 | printed += hist_browser__show_callchain(self, &entry->sorted_chain, | ||
501 | 1, row, &row_offset, | ||
502 | ¤t_entry); | ||
503 | if (current_entry) | ||
504 | self->he_selection = entry; | ||
505 | } | ||
506 | |||
507 | return printed; | ||
508 | } | ||
509 | |||
510 | static unsigned int hist_browser__refresh(struct ui_browser *self) | ||
511 | { | ||
512 | unsigned row = 0; | ||
513 | struct rb_node *nd; | ||
514 | struct hist_browser *hb = container_of(self, struct hist_browser, b); | ||
515 | |||
516 | if (self->top == NULL) | ||
517 | self->top = rb_first(&hb->hists->entries); | ||
518 | |||
519 | for (nd = self->top; nd; nd = rb_next(nd)) { | ||
520 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
521 | |||
522 | if (h->filtered) | ||
523 | continue; | ||
524 | |||
525 | row += hist_browser__show_entry(hb, h, row); | ||
526 | if (row == self->height) | ||
527 | break; | ||
528 | } | ||
529 | |||
530 | return row; | ||
531 | } | ||
532 | |||
533 | static struct rb_node *hists__filter_entries(struct rb_node *nd) | ||
534 | { | ||
535 | while (nd != NULL) { | ||
536 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
537 | if (!h->filtered) | ||
538 | return nd; | ||
539 | |||
540 | nd = rb_next(nd); | ||
541 | } | ||
542 | |||
543 | return NULL; | ||
544 | } | ||
545 | |||
546 | static struct rb_node *hists__filter_prev_entries(struct rb_node *nd) | ||
547 | { | ||
548 | while (nd != NULL) { | ||
549 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | ||
550 | if (!h->filtered) | ||
551 | return nd; | ||
552 | |||
553 | nd = rb_prev(nd); | ||
554 | } | ||
555 | |||
556 | return NULL; | ||
557 | } | ||
558 | |||
559 | static void ui_browser__hists_seek(struct ui_browser *self, | ||
560 | off_t offset, int whence) | ||
561 | { | ||
562 | struct hist_entry *h; | ||
563 | struct rb_node *nd; | ||
564 | bool first = true; | ||
565 | |||
566 | switch (whence) { | ||
567 | case SEEK_SET: | ||
568 | nd = hists__filter_entries(rb_first(self->entries)); | ||
569 | break; | ||
570 | case SEEK_CUR: | ||
571 | nd = self->top; | ||
572 | goto do_offset; | ||
573 | case SEEK_END: | ||
574 | nd = hists__filter_prev_entries(rb_last(self->entries)); | ||
575 | first = false; | ||
576 | break; | ||
577 | default: | ||
578 | return; | ||
579 | } | ||
580 | |||
581 | /* | ||
582 | * Moves not relative to the first visible entry invalidates its | ||
583 | * row_offset: | ||
584 | */ | ||
585 | h = rb_entry(self->top, struct hist_entry, rb_node); | ||
586 | h->row_offset = 0; | ||
587 | |||
588 | /* | ||
589 | * Here we have to check if nd is expanded (+), if it is we can't go | ||
590 | * the next top level hist_entry, instead we must compute an offset of | ||
591 | * what _not_ to show and not change the first visible entry. | ||
592 | * | ||
593 | * This offset increments when we are going from top to bottom and | ||
594 | * decreases when we're going from bottom to top. | ||
595 | * | ||
596 | * As we don't have backpointers to the top level in the callchains | ||
597 | * structure, we need to always print the whole hist_entry callchain, | ||
598 | * skipping the first ones that are before the first visible entry | ||
599 | * and stop when we printed enough lines to fill the screen. | ||
600 | */ | ||
601 | do_offset: | ||
602 | if (offset > 0) { | ||
603 | do { | ||
604 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
605 | if (h->ms.unfolded) { | ||
606 | u16 remaining = h->nr_rows - h->row_offset; | ||
607 | if (offset > remaining) { | ||
608 | offset -= remaining; | ||
609 | h->row_offset = 0; | ||
610 | } else { | ||
611 | h->row_offset += offset; | ||
612 | offset = 0; | ||
613 | self->top = nd; | ||
614 | break; | ||
615 | } | ||
616 | } | ||
617 | nd = hists__filter_entries(rb_next(nd)); | ||
618 | if (nd == NULL) | ||
619 | break; | ||
620 | --offset; | ||
621 | self->top = nd; | ||
622 | } while (offset != 0); | ||
623 | } else if (offset < 0) { | ||
624 | while (1) { | ||
625 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
626 | if (h->ms.unfolded) { | ||
627 | if (first) { | ||
628 | if (-offset > h->row_offset) { | ||
629 | offset += h->row_offset; | ||
630 | h->row_offset = 0; | ||
631 | } else { | ||
632 | h->row_offset += offset; | ||
633 | offset = 0; | ||
634 | self->top = nd; | ||
635 | break; | ||
636 | } | ||
637 | } else { | ||
638 | if (-offset > h->nr_rows) { | ||
639 | offset += h->nr_rows; | ||
640 | h->row_offset = 0; | ||
641 | } else { | ||
642 | h->row_offset = h->nr_rows + offset; | ||
643 | offset = 0; | ||
644 | self->top = nd; | ||
645 | break; | ||
646 | } | ||
647 | } | ||
648 | } | ||
649 | |||
650 | nd = hists__filter_prev_entries(rb_prev(nd)); | ||
651 | if (nd == NULL) | ||
652 | break; | ||
653 | ++offset; | ||
654 | self->top = nd; | ||
655 | if (offset == 0) { | ||
656 | /* | ||
657 | * Last unfiltered hist_entry, check if it is | ||
658 | * unfolded, if it is then we should have | ||
659 | * row_offset at its last entry. | ||
660 | */ | ||
661 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
662 | if (h->ms.unfolded) | ||
663 | h->row_offset = h->nr_rows; | ||
664 | break; | ||
665 | } | ||
666 | first = false; | ||
667 | } | ||
668 | } else { | ||
669 | self->top = nd; | ||
670 | h = rb_entry(nd, struct hist_entry, rb_node); | ||
671 | h->row_offset = 0; | ||
672 | } | ||
673 | } | ||
674 | |||
675 | static struct hist_browser *hist_browser__new(struct hists *hists) | ||
676 | { | ||
677 | struct hist_browser *self = zalloc(sizeof(*self)); | ||
678 | |||
679 | if (self) { | ||
680 | self->hists = hists; | ||
681 | self->b.refresh = hist_browser__refresh; | ||
682 | self->b.seek = ui_browser__hists_seek; | ||
683 | } | ||
684 | |||
685 | return self; | ||
686 | } | ||
687 | |||
688 | static void hist_browser__delete(struct hist_browser *self) | ||
689 | { | ||
690 | newtFormDestroy(self->b.form); | ||
691 | newtPopWindow(); | ||
692 | free(self); | ||
693 | } | ||
694 | |||
695 | static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) | ||
696 | { | ||
697 | return self->he_selection; | ||
698 | } | ||
699 | |||
700 | static struct thread *hist_browser__selected_thread(struct hist_browser *self) | ||
701 | { | ||
702 | return self->he_selection->thread; | ||
703 | } | ||
704 | |||
705 | static int hist_browser__title(char *bf, size_t size, const char *ev_name, | ||
706 | const struct dso *dso, const struct thread *thread) | ||
707 | { | ||
708 | int printed = 0; | ||
709 | |||
710 | if (thread) | ||
711 | printed += snprintf(bf + printed, size - printed, | ||
712 | "Thread: %s(%d)", | ||
713 | (thread->comm_set ? thread->comm : ""), | ||
714 | thread->pid); | ||
715 | if (dso) | ||
716 | printed += snprintf(bf + printed, size - printed, | ||
717 | "%sDSO: %s", thread ? " " : "", | ||
718 | dso->short_name); | ||
719 | return printed ?: snprintf(bf, size, "Event: %s", ev_name); | ||
720 | } | ||
721 | |||
722 | int hists__browse(struct hists *self, const char *helpline, const char *ev_name) | ||
723 | { | ||
724 | struct hist_browser *browser = hist_browser__new(self); | ||
725 | struct pstack *fstack; | ||
726 | const struct thread *thread_filter = NULL; | ||
727 | const struct dso *dso_filter = NULL; | ||
728 | struct newtExitStruct es; | ||
729 | char msg[160]; | ||
730 | int key = -1; | ||
731 | |||
732 | if (browser == NULL) | ||
733 | return -1; | ||
734 | |||
735 | fstack = pstack__new(2); | ||
736 | if (fstack == NULL) | ||
737 | goto out; | ||
738 | |||
739 | ui_helpline__push(helpline); | ||
740 | |||
741 | hist_browser__title(msg, sizeof(msg), ev_name, | ||
742 | dso_filter, thread_filter); | ||
743 | |||
744 | while (1) { | ||
745 | const struct thread *thread; | ||
746 | const struct dso *dso; | ||
747 | char *options[16]; | ||
748 | int nr_options = 0, choice = 0, i, | ||
749 | annotate = -2, zoom_dso = -2, zoom_thread = -2, | ||
750 | browse_map = -2; | ||
751 | |||
752 | if (hist_browser__run(browser, msg, &es)) | ||
753 | break; | ||
754 | |||
755 | thread = hist_browser__selected_thread(browser); | ||
756 | dso = browser->selection->map ? browser->selection->map->dso : NULL; | ||
757 | |||
758 | if (es.reason == NEWT_EXIT_HOTKEY) { | ||
759 | key = es.u.key; | ||
760 | |||
761 | switch (key) { | ||
762 | case NEWT_KEY_F1: | ||
763 | goto do_help; | ||
764 | case NEWT_KEY_TAB: | ||
765 | case NEWT_KEY_UNTAB: | ||
766 | /* | ||
767 | * Exit the browser, let hists__browser_tree | ||
768 | * go to the next or previous | ||
769 | */ | ||
770 | goto out_free_stack; | ||
771 | default:; | ||
772 | } | ||
773 | |||
774 | switch (key) { | ||
775 | case 'a': | ||
776 | if (browser->selection->map == NULL && | ||
777 | browser->selection->map->dso->annotate_warned) | ||
778 | continue; | ||
779 | goto do_annotate; | ||
780 | case 'd': | ||
781 | goto zoom_dso; | ||
782 | case 't': | ||
783 | goto zoom_thread; | ||
784 | case 'h': | ||
785 | case '?': | ||
786 | do_help: | ||
787 | ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n" | ||
788 | "<- Zoom out\n" | ||
789 | "a Annotate current symbol\n" | ||
790 | "h/?/F1 Show this window\n" | ||
791 | "d Zoom into current DSO\n" | ||
792 | "t Zoom into current Thread\n" | ||
793 | "q/CTRL+C Exit browser"); | ||
794 | continue; | ||
795 | default:; | ||
796 | } | ||
797 | if (is_exit_key(key)) { | ||
798 | if (key == NEWT_KEY_ESCAPE && | ||
799 | !ui__dialog_yesno("Do you really want to exit?")) | ||
800 | continue; | ||
801 | break; | ||
802 | } | ||
803 | |||
804 | if (es.u.key == NEWT_KEY_LEFT) { | ||
805 | const void *top; | ||
806 | |||
807 | if (pstack__empty(fstack)) | ||
808 | continue; | ||
809 | top = pstack__pop(fstack); | ||
810 | if (top == &dso_filter) | ||
811 | goto zoom_out_dso; | ||
812 | if (top == &thread_filter) | ||
813 | goto zoom_out_thread; | ||
814 | continue; | ||
815 | } | ||
816 | } | ||
817 | |||
818 | if (browser->selection->sym != NULL && | ||
819 | !browser->selection->map->dso->annotate_warned && | ||
820 | asprintf(&options[nr_options], "Annotate %s", | ||
821 | browser->selection->sym->name) > 0) | ||
822 | annotate = nr_options++; | ||
823 | |||
824 | if (thread != NULL && | ||
825 | asprintf(&options[nr_options], "Zoom %s %s(%d) thread", | ||
826 | (thread_filter ? "out of" : "into"), | ||
827 | (thread->comm_set ? thread->comm : ""), | ||
828 | thread->pid) > 0) | ||
829 | zoom_thread = nr_options++; | ||
830 | |||
831 | if (dso != NULL && | ||
832 | asprintf(&options[nr_options], "Zoom %s %s DSO", | ||
833 | (dso_filter ? "out of" : "into"), | ||
834 | (dso->kernel ? "the Kernel" : dso->short_name)) > 0) | ||
835 | zoom_dso = nr_options++; | ||
836 | |||
837 | if (browser->selection->map != NULL && | ||
838 | asprintf(&options[nr_options], "Browse map details") > 0) | ||
839 | browse_map = nr_options++; | ||
840 | |||
841 | options[nr_options++] = (char *)"Exit"; | ||
842 | |||
843 | choice = ui__popup_menu(nr_options, options); | ||
844 | |||
845 | for (i = 0; i < nr_options - 1; ++i) | ||
846 | free(options[i]); | ||
847 | |||
848 | if (choice == nr_options - 1) | ||
849 | break; | ||
850 | |||
851 | if (choice == -1) | ||
852 | continue; | ||
853 | |||
854 | if (choice == annotate) { | ||
855 | struct hist_entry *he; | ||
856 | do_annotate: | ||
857 | if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) { | ||
858 | browser->selection->map->dso->annotate_warned = 1; | ||
859 | ui_helpline__puts("No vmlinux file found, can't " | ||
860 | "annotate with just a " | ||
861 | "kallsyms file"); | ||
862 | continue; | ||
863 | } | ||
864 | |||
865 | he = hist_browser__selected_entry(browser); | ||
866 | if (he == NULL) | ||
867 | continue; | ||
868 | |||
869 | hist_entry__tui_annotate(he); | ||
870 | } else if (choice == browse_map) | ||
871 | map__browse(browser->selection->map); | ||
872 | else if (choice == zoom_dso) { | ||
873 | zoom_dso: | ||
874 | if (dso_filter) { | ||
875 | pstack__remove(fstack, &dso_filter); | ||
876 | zoom_out_dso: | ||
877 | ui_helpline__pop(); | ||
878 | dso_filter = NULL; | ||
879 | } else { | ||
880 | if (dso == NULL) | ||
881 | continue; | ||
882 | ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"", | ||
883 | dso->kernel ? "the Kernel" : dso->short_name); | ||
884 | dso_filter = dso; | ||
885 | pstack__push(fstack, &dso_filter); | ||
886 | } | ||
887 | hists__filter_by_dso(self, dso_filter); | ||
888 | hist_browser__title(msg, sizeof(msg), ev_name, | ||
889 | dso_filter, thread_filter); | ||
890 | hist_browser__reset(browser); | ||
891 | } else if (choice == zoom_thread) { | ||
892 | zoom_thread: | ||
893 | if (thread_filter) { | ||
894 | pstack__remove(fstack, &thread_filter); | ||
895 | zoom_out_thread: | ||
896 | ui_helpline__pop(); | ||
897 | thread_filter = NULL; | ||
898 | } else { | ||
899 | ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", | ||
900 | thread->comm_set ? thread->comm : "", | ||
901 | thread->pid); | ||
902 | thread_filter = thread; | ||
903 | pstack__push(fstack, &thread_filter); | ||
904 | } | ||
905 | hists__filter_by_thread(self, thread_filter); | ||
906 | hist_browser__title(msg, sizeof(msg), ev_name, | ||
907 | dso_filter, thread_filter); | ||
908 | hist_browser__reset(browser); | ||
909 | } | ||
910 | } | ||
911 | out_free_stack: | ||
912 | pstack__delete(fstack); | ||
913 | out: | ||
914 | hist_browser__delete(browser); | ||
915 | return key; | ||
916 | } | ||
917 | |||
918 | int hists__tui_browse_tree(struct rb_root *self, const char *help) | ||
919 | { | ||
920 | struct rb_node *first = rb_first(self), *nd = first, *next; | ||
921 | int key = 0; | ||
922 | |||
923 | while (nd) { | ||
924 | struct hists *hists = rb_entry(nd, struct hists, rb_node); | ||
925 | const char *ev_name = __event_name(hists->type, hists->config); | ||
926 | |||
927 | key = hists__browse(hists, help, ev_name); | ||
928 | |||
929 | if (is_exit_key(key)) | ||
930 | break; | ||
931 | |||
932 | switch (key) { | ||
933 | case NEWT_KEY_TAB: | ||
934 | next = rb_next(nd); | ||
935 | if (next) | ||
936 | nd = next; | ||
937 | break; | ||
938 | case NEWT_KEY_UNTAB: | ||
939 | if (nd == first) | ||
940 | continue; | ||
941 | nd = rb_prev(nd); | ||
942 | default: | ||
943 | break; | ||
944 | } | ||
945 | } | ||
946 | |||
947 | return key; | ||
948 | } | ||
diff --git a/tools/perf/util/ui/browsers/map.c b/tools/perf/util/ui/browsers/map.c new file mode 100644 index 000000000000..142b825b42bf --- /dev/null +++ b/tools/perf/util/ui/browsers/map.c | |||
@@ -0,0 +1,161 @@ | |||
1 | #include "../libslang.h" | ||
2 | #include <elf.h> | ||
3 | #include <newt.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 0; | ||
45 | } | ||
46 | |||
47 | struct map_browser { | ||
48 | struct ui_browser b; | ||
49 | struct map *map; | ||
50 | u16 namelen; | ||
51 | u8 addrlen; | ||
52 | }; | ||
53 | |||
54 | static void map_browser__write(struct ui_browser *self, void *nd, int row) | ||
55 | { | ||
56 | struct symbol *sym = rb_entry(nd, struct symbol, rb_node); | ||
57 | struct map_browser *mb = container_of(self, struct map_browser, b); | ||
58 | bool current_entry = ui_browser__is_current_entry(self, row); | ||
59 | int color = ui_browser__percent_color(0, current_entry); | ||
60 | |||
61 | SLsmg_set_color(color); | ||
62 | slsmg_printf("%*llx %*llx %c ", | ||
63 | mb->addrlen, sym->start, mb->addrlen, sym->end, | ||
64 | sym->binding == STB_GLOBAL ? 'g' : | ||
65 | sym->binding == STB_LOCAL ? 'l' : 'w'); | ||
66 | slsmg_write_nstring(sym->name, mb->namelen); | ||
67 | } | ||
68 | |||
69 | /* FIXME uber-kludgy, see comment on cmd_report... */ | ||
70 | static u32 *symbol__browser_index(struct symbol *self) | ||
71 | { | ||
72 | return ((void *)self) - sizeof(struct rb_node) - sizeof(u32); | ||
73 | } | ||
74 | |||
75 | static int map_browser__search(struct map_browser *self) | ||
76 | { | ||
77 | char target[512]; | ||
78 | struct symbol *sym; | ||
79 | int err = ui_entry__read("Search by name/addr", target, sizeof(target), 40); | ||
80 | |||
81 | if (err) | ||
82 | return err; | ||
83 | |||
84 | if (target[0] == '0' && tolower(target[1]) == 'x') { | ||
85 | u64 addr = strtoull(target, NULL, 16); | ||
86 | sym = map__find_symbol(self->map, addr, NULL); | ||
87 | } else | ||
88 | sym = map__find_symbol_by_name(self->map, target, NULL); | ||
89 | |||
90 | if (sym != NULL) { | ||
91 | u32 *idx = symbol__browser_index(sym); | ||
92 | |||
93 | self->b.top = &sym->rb_node; | ||
94 | self->b.index = self->b.top_idx = *idx; | ||
95 | } else | ||
96 | ui_helpline__fpush("%s not found!", target); | ||
97 | |||
98 | return 0; | ||
99 | } | ||
100 | |||
101 | static int map_browser__run(struct map_browser *self, struct newtExitStruct *es) | ||
102 | { | ||
103 | if (ui_browser__show(&self->b, self->map->dso->long_name, | ||
104 | "Press <- or ESC to exit, %s / to search", | ||
105 | verbose ? "" : "restart with -v to use") < 0) | ||
106 | return -1; | ||
107 | |||
108 | newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); | ||
109 | newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER); | ||
110 | if (verbose) | ||
111 | newtFormAddHotKey(self->b.form, '/'); | ||
112 | |||
113 | while (1) { | ||
114 | ui_browser__run(&self->b, es); | ||
115 | |||
116 | if (es->reason != NEWT_EXIT_HOTKEY) | ||
117 | break; | ||
118 | if (verbose && es->u.key == '/') | ||
119 | map_browser__search(self); | ||
120 | else | ||
121 | break; | ||
122 | } | ||
123 | |||
124 | ui_browser__hide(&self->b); | ||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | int map__browse(struct map *self) | ||
129 | { | ||
130 | struct map_browser mb = { | ||
131 | .b = { | ||
132 | .entries = &self->dso->symbols[self->type], | ||
133 | .refresh = ui_browser__rb_tree_refresh, | ||
134 | .seek = ui_browser__rb_tree_seek, | ||
135 | .write = map_browser__write, | ||
136 | }, | ||
137 | .map = self, | ||
138 | }; | ||
139 | struct newtExitStruct es; | ||
140 | struct rb_node *nd; | ||
141 | char tmp[BITS_PER_LONG / 4]; | ||
142 | u64 maxaddr = 0; | ||
143 | |||
144 | for (nd = rb_first(mb.b.entries); nd; nd = rb_next(nd)) { | ||
145 | struct symbol *pos = rb_entry(nd, struct symbol, rb_node); | ||
146 | |||
147 | if (mb.namelen < pos->namelen) | ||
148 | mb.namelen = pos->namelen; | ||
149 | if (maxaddr < pos->end) | ||
150 | maxaddr = pos->end; | ||
151 | if (verbose) { | ||
152 | u32 *idx = symbol__browser_index(pos); | ||
153 | *idx = mb.b.nr_entries; | ||
154 | } | ||
155 | ++mb.b.nr_entries; | ||
156 | } | ||
157 | |||
158 | mb.addrlen = snprintf(tmp, sizeof(tmp), "%llx", maxaddr); | ||
159 | mb.b.width += mb.addrlen * 2 + 4 + mb.namelen; | ||
160 | return map_browser__run(&mb, &es); | ||
161 | } | ||
diff --git a/tools/perf/util/ui/browsers/map.h b/tools/perf/util/ui/browsers/map.h new file mode 100644 index 000000000000..df8581a43e17 --- /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/helpline.c b/tools/perf/util/ui/helpline.c new file mode 100644 index 000000000000..8d79daa4458a --- /dev/null +++ b/tools/perf/util/ui/helpline.c | |||
@@ -0,0 +1,69 @@ | |||
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 | |||
9 | void ui_helpline__pop(void) | ||
10 | { | ||
11 | newtPopHelpLine(); | ||
12 | } | ||
13 | |||
14 | void ui_helpline__push(const char *msg) | ||
15 | { | ||
16 | newtPushHelpLine(msg); | ||
17 | } | ||
18 | |||
19 | void ui_helpline__vpush(const char *fmt, va_list ap) | ||
20 | { | ||
21 | char *s; | ||
22 | |||
23 | if (vasprintf(&s, fmt, ap) < 0) | ||
24 | vfprintf(stderr, fmt, ap); | ||
25 | else { | ||
26 | ui_helpline__push(s); | ||
27 | free(s); | ||
28 | } | ||
29 | } | ||
30 | |||
31 | void ui_helpline__fpush(const char *fmt, ...) | ||
32 | { | ||
33 | va_list ap; | ||
34 | |||
35 | va_start(ap, fmt); | ||
36 | ui_helpline__vpush(fmt, ap); | ||
37 | va_end(ap); | ||
38 | } | ||
39 | |||
40 | void ui_helpline__puts(const char *msg) | ||
41 | { | ||
42 | ui_helpline__pop(); | ||
43 | ui_helpline__push(msg); | ||
44 | } | ||
45 | |||
46 | void ui_helpline__init(void) | ||
47 | { | ||
48 | ui_helpline__puts(" "); | ||
49 | } | ||
50 | |||
51 | char ui_helpline__last_msg[1024]; | ||
52 | |||
53 | int ui_helpline__show_help(const char *format, va_list ap) | ||
54 | { | ||
55 | int ret; | ||
56 | static int backlog; | ||
57 | |||
58 | ret = vsnprintf(ui_helpline__last_msg + backlog, | ||
59 | sizeof(ui_helpline__last_msg) - backlog, format, ap); | ||
60 | backlog += ret; | ||
61 | |||
62 | if (ui_helpline__last_msg[backlog - 1] == '\n') { | ||
63 | ui_helpline__puts(ui_helpline__last_msg); | ||
64 | newtRefresh(); | ||
65 | backlog = 0; | ||
66 | } | ||
67 | |||
68 | return ret; | ||
69 | } | ||
diff --git a/tools/perf/util/ui/helpline.h b/tools/perf/util/ui/helpline.h new file mode 100644 index 000000000000..ab6028d0c401 --- /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 000000000000..5623da8e8080 --- /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 000000000000..d7fc399d36b3 --- /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 000000000000..a3820a0beb5b --- /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 000000000000..662085032eb7 --- /dev/null +++ b/tools/perf/util/ui/setup.c | |||
@@ -0,0 +1,42 @@ | |||
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 | |||
10 | static void newt_suspend(void *d __used) | ||
11 | { | ||
12 | newtSuspend(); | ||
13 | raise(SIGTSTP); | ||
14 | newtResume(); | ||
15 | } | ||
16 | |||
17 | void setup_browser(void) | ||
18 | { | ||
19 | if (!isatty(1) || !use_browser || dump_trace) { | ||
20 | use_browser = 0; | ||
21 | setup_pager(); | ||
22 | return; | ||
23 | } | ||
24 | |||
25 | use_browser = 1; | ||
26 | newtInit(); | ||
27 | newtCls(); | ||
28 | newtSetSuspendCallback(newt_suspend, NULL); | ||
29 | ui_helpline__init(); | ||
30 | ui_browser__init(); | ||
31 | } | ||
32 | |||
33 | void exit_browser(bool wait_for_ok) | ||
34 | { | ||
35 | if (use_browser > 0) { | ||
36 | if (wait_for_ok) { | ||
37 | char title[] = "Fatal Error", ok[] = "Ok"; | ||
38 | newtWinMessage(title, ok, ui_helpline__last_msg); | ||
39 | } | ||
40 | newtFinished(); | ||
41 | } | ||
42 | } | ||
diff --git a/tools/perf/util/ui/util.c b/tools/perf/util/ui/util.c new file mode 100644 index 000000000000..04600e26ceea --- /dev/null +++ b/tools/perf/util/ui/util.c | |||
@@ -0,0 +1,114 @@ | |||
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 "util.h" | ||
13 | |||
14 | newtComponent newt_form__new(void); | ||
15 | |||
16 | static void newt_form__set_exit_keys(newtComponent self) | ||
17 | { | ||
18 | newtFormAddHotKey(self, NEWT_KEY_LEFT); | ||
19 | newtFormAddHotKey(self, NEWT_KEY_ESCAPE); | ||
20 | newtFormAddHotKey(self, 'Q'); | ||
21 | newtFormAddHotKey(self, 'q'); | ||
22 | newtFormAddHotKey(self, CTRL('c')); | ||
23 | } | ||
24 | |||
25 | newtComponent newt_form__new(void) | ||
26 | { | ||
27 | newtComponent self = newtForm(NULL, NULL, 0); | ||
28 | if (self) | ||
29 | newt_form__set_exit_keys(self); | ||
30 | return self; | ||
31 | } | ||
32 | |||
33 | int ui__popup_menu(int argc, char * const argv[]) | ||
34 | { | ||
35 | struct newtExitStruct es; | ||
36 | int i, rc = -1, max_len = 5; | ||
37 | newtComponent listbox, form = newt_form__new(); | ||
38 | |||
39 | if (form == NULL) | ||
40 | return -1; | ||
41 | |||
42 | listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT); | ||
43 | if (listbox == NULL) | ||
44 | goto out_destroy_form; | ||
45 | |||
46 | newtFormAddComponent(form, listbox); | ||
47 | |||
48 | for (i = 0; i < argc; ++i) { | ||
49 | int len = strlen(argv[i]); | ||
50 | if (len > max_len) | ||
51 | max_len = len; | ||
52 | if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i)) | ||
53 | goto out_destroy_form; | ||
54 | } | ||
55 | |||
56 | newtCenteredWindow(max_len, argc, NULL); | ||
57 | newtFormRun(form, &es); | ||
58 | rc = newtListboxGetCurrent(listbox) - NULL; | ||
59 | if (es.reason == NEWT_EXIT_HOTKEY) | ||
60 | rc = -1; | ||
61 | newtPopWindow(); | ||
62 | out_destroy_form: | ||
63 | newtFormDestroy(form); | ||
64 | return rc; | ||
65 | } | ||
66 | |||
67 | int ui__help_window(const char *text) | ||
68 | { | ||
69 | struct newtExitStruct es; | ||
70 | newtComponent tb, form = newt_form__new(); | ||
71 | int rc = -1; | ||
72 | int max_len = 0, nr_lines = 0; | ||
73 | const char *t; | ||
74 | |||
75 | if (form == NULL) | ||
76 | return -1; | ||
77 | |||
78 | t = text; | ||
79 | while (1) { | ||
80 | const char *sep = strchr(t, '\n'); | ||
81 | int len; | ||
82 | |||
83 | if (sep == NULL) | ||
84 | sep = strchr(t, '\0'); | ||
85 | len = sep - t; | ||
86 | if (max_len < len) | ||
87 | max_len = len; | ||
88 | ++nr_lines; | ||
89 | if (*sep == '\0') | ||
90 | break; | ||
91 | t = sep + 1; | ||
92 | } | ||
93 | |||
94 | tb = newtTextbox(0, 0, max_len, nr_lines, 0); | ||
95 | if (tb == NULL) | ||
96 | goto out_destroy_form; | ||
97 | |||
98 | newtTextboxSetText(tb, text); | ||
99 | newtFormAddComponent(form, tb); | ||
100 | newtCenteredWindow(max_len, nr_lines, NULL); | ||
101 | newtFormRun(form, &es); | ||
102 | newtPopWindow(); | ||
103 | rc = 0; | ||
104 | out_destroy_form: | ||
105 | newtFormDestroy(form); | ||
106 | return rc; | ||
107 | } | ||
108 | |||
109 | bool ui__dialog_yesno(const char *msg) | ||
110 | { | ||
111 | /* newtWinChoice should really be accepting const char pointers... */ | ||
112 | char yes[] = "Yes", no[] = "No"; | ||
113 | return newtWinChoice(NULL, yes, no, (char *)msg) == 1; | ||
114 | } | ||
diff --git a/tools/perf/util/ui/util.h b/tools/perf/util/ui/util.h new file mode 100644 index 000000000000..afcbc1d99531 --- /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/perf/util/util.h b/tools/perf/util/util.h index 4e8b6b0c551c..f380fed74359 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h | |||
@@ -89,6 +89,7 @@ | |||
89 | 89 | ||
90 | extern const char *graph_line; | 90 | extern const char *graph_line; |
91 | extern const char *graph_dotted_line; | 91 | extern const char *graph_dotted_line; |
92 | extern char buildid_dir[]; | ||
92 | 93 | ||
93 | /* On most systems <limits.h> would have given us this, but | 94 | /* On most systems <limits.h> would have given us this, but |
94 | * not on some systems (e.g. GNU/Hurd). | 95 | * not on some systems (e.g. GNU/Hurd). |
@@ -152,6 +153,8 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))) | |||
152 | extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN); | 153 | extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN); |
153 | 154 | ||
154 | extern int prefixcmp(const char *str, const char *prefix); | 155 | extern int prefixcmp(const char *str, const char *prefix); |
156 | extern void set_buildid_dir(void); | ||
157 | extern void disable_buildid_cache(void); | ||
155 | 158 | ||
156 | static inline const char *skip_prefix(const char *str, const char *prefix) | 159 | static inline const char *skip_prefix(const char *str, const char *prefix) |
157 | { | 160 | { |