diff options
Diffstat (limited to 'tools/perf/builtin-script.c')
-rw-r--r-- | tools/perf/builtin-script.c | 310 |
1 files changed, 279 insertions, 31 deletions
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index b766c2a9ac97..ac574ea23917 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c | |||
@@ -12,6 +12,8 @@ | |||
12 | #include "util/trace-event.h" | 12 | #include "util/trace-event.h" |
13 | #include "util/parse-options.h" | 13 | #include "util/parse-options.h" |
14 | #include "util/util.h" | 14 | #include "util/util.h" |
15 | #include "util/evlist.h" | ||
16 | #include "util/evsel.h" | ||
15 | 17 | ||
16 | static char const *script_name; | 18 | static char const *script_name; |
17 | static char const *generate_script_lang; | 19 | static char const *generate_script_lang; |
@@ -19,6 +21,175 @@ static bool debug_mode; | |||
19 | static u64 last_timestamp; | 21 | static u64 last_timestamp; |
20 | static u64 nr_unordered; | 22 | static u64 nr_unordered; |
21 | extern const struct option record_options[]; | 23 | extern const struct option record_options[]; |
24 | static bool no_callchain; | ||
25 | |||
26 | enum perf_output_field { | ||
27 | PERF_OUTPUT_COMM = 1U << 0, | ||
28 | PERF_OUTPUT_TID = 1U << 1, | ||
29 | PERF_OUTPUT_PID = 1U << 2, | ||
30 | PERF_OUTPUT_TIME = 1U << 3, | ||
31 | PERF_OUTPUT_CPU = 1U << 4, | ||
32 | PERF_OUTPUT_EVNAME = 1U << 5, | ||
33 | PERF_OUTPUT_TRACE = 1U << 6, | ||
34 | PERF_OUTPUT_SYM = 1U << 7, | ||
35 | }; | ||
36 | |||
37 | struct output_option { | ||
38 | const char *str; | ||
39 | enum perf_output_field field; | ||
40 | } all_output_options[] = { | ||
41 | {.str = "comm", .field = PERF_OUTPUT_COMM}, | ||
42 | {.str = "tid", .field = PERF_OUTPUT_TID}, | ||
43 | {.str = "pid", .field = PERF_OUTPUT_PID}, | ||
44 | {.str = "time", .field = PERF_OUTPUT_TIME}, | ||
45 | {.str = "cpu", .field = PERF_OUTPUT_CPU}, | ||
46 | {.str = "event", .field = PERF_OUTPUT_EVNAME}, | ||
47 | {.str = "trace", .field = PERF_OUTPUT_TRACE}, | ||
48 | {.str = "sym", .field = PERF_OUTPUT_SYM}, | ||
49 | }; | ||
50 | |||
51 | /* default set to maintain compatibility with current format */ | ||
52 | static u64 output_fields[PERF_TYPE_MAX] = { | ||
53 | [PERF_TYPE_HARDWARE] = PERF_OUTPUT_COMM | PERF_OUTPUT_TID | \ | ||
54 | PERF_OUTPUT_CPU | PERF_OUTPUT_TIME | \ | ||
55 | PERF_OUTPUT_EVNAME | PERF_OUTPUT_SYM, | ||
56 | |||
57 | [PERF_TYPE_SOFTWARE] = PERF_OUTPUT_COMM | PERF_OUTPUT_TID | \ | ||
58 | PERF_OUTPUT_CPU | PERF_OUTPUT_TIME | \ | ||
59 | PERF_OUTPUT_EVNAME | PERF_OUTPUT_SYM, | ||
60 | |||
61 | [PERF_TYPE_TRACEPOINT] = PERF_OUTPUT_COMM | PERF_OUTPUT_TID | \ | ||
62 | PERF_OUTPUT_CPU | PERF_OUTPUT_TIME | \ | ||
63 | PERF_OUTPUT_EVNAME | PERF_OUTPUT_TRACE, | ||
64 | }; | ||
65 | |||
66 | static bool output_set_by_user; | ||
67 | |||
68 | #define PRINT_FIELD(x) (output_fields[attr->type] & PERF_OUTPUT_##x) | ||
69 | |||
70 | static int perf_session__check_attr(struct perf_session *session, | ||
71 | struct perf_event_attr *attr) | ||
72 | { | ||
73 | if (PRINT_FIELD(TRACE) && | ||
74 | !perf_session__has_traces(session, "record -R")) | ||
75 | return -EINVAL; | ||
76 | |||
77 | if (PRINT_FIELD(SYM)) { | ||
78 | if (!(session->sample_type & PERF_SAMPLE_IP)) { | ||
79 | pr_err("Samples do not contain IP data.\n"); | ||
80 | return -EINVAL; | ||
81 | } | ||
82 | if (!no_callchain && | ||
83 | !(session->sample_type & PERF_SAMPLE_CALLCHAIN)) | ||
84 | symbol_conf.use_callchain = false; | ||
85 | } | ||
86 | |||
87 | if ((PRINT_FIELD(PID) || PRINT_FIELD(TID)) && | ||
88 | !(session->sample_type & PERF_SAMPLE_TID)) { | ||
89 | pr_err("Samples do not contain TID/PID data.\n"); | ||
90 | return -EINVAL; | ||
91 | } | ||
92 | |||
93 | if (PRINT_FIELD(TIME) && | ||
94 | !(session->sample_type & PERF_SAMPLE_TIME)) { | ||
95 | pr_err("Samples do not contain timestamps.\n"); | ||
96 | return -EINVAL; | ||
97 | } | ||
98 | |||
99 | if (PRINT_FIELD(CPU) && | ||
100 | !(session->sample_type & PERF_SAMPLE_CPU)) { | ||
101 | pr_err("Samples do not contain cpu.\n"); | ||
102 | return -EINVAL; | ||
103 | } | ||
104 | |||
105 | return 0; | ||
106 | } | ||
107 | |||
108 | static void print_sample_start(struct perf_sample *sample, | ||
109 | struct thread *thread, | ||
110 | struct perf_event_attr *attr) | ||
111 | { | ||
112 | int type; | ||
113 | struct event *event; | ||
114 | const char *evname = NULL; | ||
115 | unsigned long secs; | ||
116 | unsigned long usecs; | ||
117 | unsigned long long nsecs; | ||
118 | |||
119 | if (PRINT_FIELD(COMM)) { | ||
120 | if (latency_format) | ||
121 | printf("%8.8s ", thread->comm); | ||
122 | else if (PRINT_FIELD(SYM) && symbol_conf.use_callchain) | ||
123 | printf("%s ", thread->comm); | ||
124 | else | ||
125 | printf("%16s ", thread->comm); | ||
126 | } | ||
127 | |||
128 | if (PRINT_FIELD(PID) && PRINT_FIELD(TID)) | ||
129 | printf("%5d/%-5d ", sample->pid, sample->tid); | ||
130 | else if (PRINT_FIELD(PID)) | ||
131 | printf("%5d ", sample->pid); | ||
132 | else if (PRINT_FIELD(TID)) | ||
133 | printf("%5d ", sample->tid); | ||
134 | |||
135 | if (PRINT_FIELD(CPU)) { | ||
136 | if (latency_format) | ||
137 | printf("%3d ", sample->cpu); | ||
138 | else | ||
139 | printf("[%03d] ", sample->cpu); | ||
140 | } | ||
141 | |||
142 | if (PRINT_FIELD(TIME)) { | ||
143 | nsecs = sample->time; | ||
144 | secs = nsecs / NSECS_PER_SEC; | ||
145 | nsecs -= secs * NSECS_PER_SEC; | ||
146 | usecs = nsecs / NSECS_PER_USEC; | ||
147 | printf("%5lu.%06lu: ", secs, usecs); | ||
148 | } | ||
149 | |||
150 | if (PRINT_FIELD(EVNAME)) { | ||
151 | if (attr->type == PERF_TYPE_TRACEPOINT) { | ||
152 | type = trace_parse_common_type(sample->raw_data); | ||
153 | event = trace_find_event(type); | ||
154 | if (event) | ||
155 | evname = event->name; | ||
156 | } else | ||
157 | evname = __event_name(attr->type, attr->config); | ||
158 | |||
159 | printf("%s: ", evname ? evname : "(unknown)"); | ||
160 | } | ||
161 | } | ||
162 | |||
163 | static void process_event(union perf_event *event __unused, | ||
164 | struct perf_sample *sample, | ||
165 | struct perf_evsel *evsel, | ||
166 | struct perf_session *session, | ||
167 | struct thread *thread) | ||
168 | { | ||
169 | struct perf_event_attr *attr = &evsel->attr; | ||
170 | |||
171 | if (output_fields[attr->type] == 0) | ||
172 | return; | ||
173 | |||
174 | if (perf_session__check_attr(session, attr) < 0) | ||
175 | return; | ||
176 | |||
177 | print_sample_start(sample, thread, attr); | ||
178 | |||
179 | if (PRINT_FIELD(TRACE)) | ||
180 | print_trace_event(sample->cpu, sample->raw_data, | ||
181 | sample->raw_size); | ||
182 | |||
183 | if (PRINT_FIELD(SYM)) { | ||
184 | if (!symbol_conf.use_callchain) | ||
185 | printf(" "); | ||
186 | else | ||
187 | printf("\n"); | ||
188 | perf_session__print_symbols(event, sample, session); | ||
189 | } | ||
190 | |||
191 | printf("\n"); | ||
192 | } | ||
22 | 193 | ||
23 | static int default_start_script(const char *script __unused, | 194 | static int default_start_script(const char *script __unused, |
24 | int argc __unused, | 195 | int argc __unused, |
@@ -40,7 +211,7 @@ static int default_generate_script(const char *outfile __unused) | |||
40 | static struct scripting_ops default_scripting_ops = { | 211 | static struct scripting_ops default_scripting_ops = { |
41 | .start_script = default_start_script, | 212 | .start_script = default_start_script, |
42 | .stop_script = default_stop_script, | 213 | .stop_script = default_stop_script, |
43 | .process_event = print_event, | 214 | .process_event = process_event, |
44 | .generate_script = default_generate_script, | 215 | .generate_script = default_generate_script, |
45 | }; | 216 | }; |
46 | 217 | ||
@@ -63,7 +234,9 @@ static int cleanup_scripting(void) | |||
63 | 234 | ||
64 | static char const *input_name = "perf.data"; | 235 | static char const *input_name = "perf.data"; |
65 | 236 | ||
66 | static int process_sample_event(event_t *event, struct sample_data *sample, | 237 | static int process_sample_event(union perf_event *event, |
238 | struct perf_sample *sample, | ||
239 | struct perf_evsel *evsel, | ||
67 | struct perf_session *session) | 240 | struct perf_session *session) |
68 | { | 241 | { |
69 | struct thread *thread = perf_session__findnew(session, event->ip.pid); | 242 | struct thread *thread = perf_session__findnew(session, event->ip.pid); |
@@ -74,40 +247,34 @@ static int process_sample_event(event_t *event, struct sample_data *sample, | |||
74 | return -1; | 247 | return -1; |
75 | } | 248 | } |
76 | 249 | ||
77 | if (session->sample_type & PERF_SAMPLE_RAW) { | 250 | if (debug_mode) { |
78 | if (debug_mode) { | 251 | if (sample->time < last_timestamp) { |
79 | if (sample->time < last_timestamp) { | 252 | pr_err("Samples misordered, previous: %" PRIu64 |
80 | pr_err("Samples misordered, previous: %" PRIu64 | 253 | " this: %" PRIu64 "\n", last_timestamp, |
81 | " this: %" PRIu64 "\n", last_timestamp, | 254 | sample->time); |
82 | sample->time); | 255 | nr_unordered++; |
83 | nr_unordered++; | ||
84 | } | ||
85 | last_timestamp = sample->time; | ||
86 | return 0; | ||
87 | } | 256 | } |
88 | /* | 257 | last_timestamp = sample->time; |
89 | * FIXME: better resolve from pid from the struct trace_entry | 258 | return 0; |
90 | * field, although it should be the same than this perf | ||
91 | * event pid | ||
92 | */ | ||
93 | scripting_ops->process_event(sample->cpu, sample->raw_data, | ||
94 | sample->raw_size, | ||
95 | sample->time, thread->comm); | ||
96 | } | 259 | } |
260 | scripting_ops->process_event(event, sample, evsel, session, thread); | ||
97 | 261 | ||
98 | session->hists.stats.total_period += sample->period; | 262 | session->hists.stats.total_period += sample->period; |
99 | return 0; | 263 | return 0; |
100 | } | 264 | } |
101 | 265 | ||
102 | static struct perf_event_ops event_ops = { | 266 | static struct perf_event_ops event_ops = { |
103 | .sample = process_sample_event, | 267 | .sample = process_sample_event, |
104 | .comm = event__process_comm, | 268 | .mmap = perf_event__process_mmap, |
105 | .attr = event__process_attr, | 269 | .comm = perf_event__process_comm, |
106 | .event_type = event__process_event_type, | 270 | .exit = perf_event__process_task, |
107 | .tracing_data = event__process_tracing_data, | 271 | .fork = perf_event__process_task, |
108 | .build_id = event__process_build_id, | 272 | .attr = perf_event__process_attr, |
109 | .ordering_requires_timestamps = true, | 273 | .event_type = perf_event__process_event_type, |
274 | .tracing_data = perf_event__process_tracing_data, | ||
275 | .build_id = perf_event__process_build_id, | ||
110 | .ordered_samples = true, | 276 | .ordered_samples = true, |
277 | .ordering_requires_timestamps = true, | ||
111 | }; | 278 | }; |
112 | 279 | ||
113 | extern volatile int session_done; | 280 | extern volatile int session_done; |
@@ -279,6 +446,68 @@ static int parse_scriptname(const struct option *opt __used, | |||
279 | return 0; | 446 | return 0; |
280 | } | 447 | } |
281 | 448 | ||
449 | static int parse_output_fields(const struct option *opt __used, | ||
450 | const char *arg, int unset __used) | ||
451 | { | ||
452 | char *tok; | ||
453 | int i, imax = sizeof(all_output_options) / sizeof(struct output_option); | ||
454 | int rc = 0; | ||
455 | char *str = strdup(arg); | ||
456 | int type = -1; | ||
457 | |||
458 | if (!str) | ||
459 | return -ENOMEM; | ||
460 | |||
461 | tok = strtok(str, ":"); | ||
462 | if (!tok) { | ||
463 | fprintf(stderr, | ||
464 | "Invalid field string - not prepended with type."); | ||
465 | return -EINVAL; | ||
466 | } | ||
467 | |||
468 | /* first word should state which event type user | ||
469 | * is specifying the fields | ||
470 | */ | ||
471 | if (!strcmp(tok, "hw")) | ||
472 | type = PERF_TYPE_HARDWARE; | ||
473 | else if (!strcmp(tok, "sw")) | ||
474 | type = PERF_TYPE_SOFTWARE; | ||
475 | else if (!strcmp(tok, "trace")) | ||
476 | type = PERF_TYPE_TRACEPOINT; | ||
477 | else { | ||
478 | fprintf(stderr, "Invalid event type in field string."); | ||
479 | return -EINVAL; | ||
480 | } | ||
481 | |||
482 | output_fields[type] = 0; | ||
483 | while (1) { | ||
484 | tok = strtok(NULL, ","); | ||
485 | if (!tok) | ||
486 | break; | ||
487 | for (i = 0; i < imax; ++i) { | ||
488 | if (strcmp(tok, all_output_options[i].str) == 0) { | ||
489 | output_fields[type] |= all_output_options[i].field; | ||
490 | break; | ||
491 | } | ||
492 | } | ||
493 | if (i == imax) { | ||
494 | fprintf(stderr, "Invalid field requested."); | ||
495 | rc = -EINVAL; | ||
496 | break; | ||
497 | } | ||
498 | } | ||
499 | |||
500 | if (output_fields[type] == 0) { | ||
501 | pr_debug("No fields requested for %s type. " | ||
502 | "Events will not be displayed\n", event_type(type)); | ||
503 | } | ||
504 | |||
505 | output_set_by_user = true; | ||
506 | |||
507 | free(str); | ||
508 | return rc; | ||
509 | } | ||
510 | |||
282 | /* Helper function for filesystems that return a dent->d_type DT_UNKNOWN */ | 511 | /* Helper function for filesystems that return a dent->d_type DT_UNKNOWN */ |
283 | static int is_directory(const char *base_path, const struct dirent *dent) | 512 | static int is_directory(const char *base_path, const struct dirent *dent) |
284 | { | 513 | { |
@@ -591,6 +820,17 @@ static const struct option options[] = { | |||
591 | "input file name"), | 820 | "input file name"), |
592 | OPT_BOOLEAN('d', "debug-mode", &debug_mode, | 821 | OPT_BOOLEAN('d', "debug-mode", &debug_mode, |
593 | "do various checks like samples ordering and lost events"), | 822 | "do various checks like samples ordering and lost events"), |
823 | OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, | ||
824 | "file", "vmlinux pathname"), | ||
825 | OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, | ||
826 | "file", "kallsyms pathname"), | ||
827 | OPT_BOOLEAN('G', "hide-call-graph", &no_callchain, | ||
828 | "When printing symbols do not display call chain"), | ||
829 | OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", | ||
830 | "Look for files with symbols relative to this directory"), | ||
831 | OPT_CALLBACK('f', "fields", NULL, "str", | ||
832 | "comma separated output fields prepend with 'type:'. Valid types: hw,sw,trace. Fields: comm,tid,pid,time,cpu,event,trace,sym", | ||
833 | parse_output_fields), | ||
594 | 834 | ||
595 | OPT_END() | 835 | OPT_END() |
596 | }; | 836 | }; |
@@ -771,14 +1011,22 @@ int cmd_script(int argc, const char **argv, const char *prefix __used) | |||
771 | if (session == NULL) | 1011 | if (session == NULL) |
772 | return -ENOMEM; | 1012 | return -ENOMEM; |
773 | 1013 | ||
774 | if (strcmp(input_name, "-") && | 1014 | if (!no_callchain) |
775 | !perf_session__has_traces(session, "record -R")) | 1015 | symbol_conf.use_callchain = true; |
776 | return -EINVAL; | 1016 | else |
1017 | symbol_conf.use_callchain = false; | ||
777 | 1018 | ||
778 | if (generate_script_lang) { | 1019 | if (generate_script_lang) { |
779 | struct stat perf_stat; | 1020 | struct stat perf_stat; |
1021 | int input; | ||
1022 | |||
1023 | if (output_set_by_user) { | ||
1024 | fprintf(stderr, | ||
1025 | "custom fields not supported for generated scripts"); | ||
1026 | return -1; | ||
1027 | } | ||
780 | 1028 | ||
781 | int input = open(input_name, O_RDONLY); | 1029 | input = open(input_name, O_RDONLY); |
782 | if (input < 0) { | 1030 | if (input < 0) { |
783 | perror("failed to open file"); | 1031 | perror("failed to open file"); |
784 | exit(-1); | 1032 | exit(-1); |