diff options
Diffstat (limited to 'tools/perf/builtin-script.c')
-rw-r--r-- | tools/perf/builtin-script.c | 300 |
1 files changed, 277 insertions, 23 deletions
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index 5f40df635dc..9f5fc549214 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,183 @@ 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_session *session, | ||
166 | struct thread *thread) | ||
167 | { | ||
168 | struct perf_event_attr *attr; | ||
169 | struct perf_evsel *evsel; | ||
170 | |||
171 | evsel = perf_evlist__id2evsel(session->evlist, sample->id); | ||
172 | if (evsel == NULL) { | ||
173 | pr_err("Invalid data. Contains samples with id not in " | ||
174 | "its header!\n"); | ||
175 | return; | ||
176 | } | ||
177 | attr = &evsel->attr; | ||
178 | |||
179 | if (output_fields[attr->type] == 0) | ||
180 | return; | ||
181 | |||
182 | if (perf_session__check_attr(session, attr) < 0) | ||
183 | return; | ||
184 | |||
185 | print_sample_start(sample, thread, attr); | ||
186 | |||
187 | if (PRINT_FIELD(TRACE)) | ||
188 | print_trace_event(sample->cpu, sample->raw_data, | ||
189 | sample->raw_size); | ||
190 | |||
191 | if (PRINT_FIELD(SYM)) { | ||
192 | if (!symbol_conf.use_callchain) | ||
193 | printf(" "); | ||
194 | else | ||
195 | printf("\n"); | ||
196 | perf_session__print_symbols(event, sample, session); | ||
197 | } | ||
198 | |||
199 | printf("\n"); | ||
200 | } | ||
22 | 201 | ||
23 | static int default_start_script(const char *script __unused, | 202 | static int default_start_script(const char *script __unused, |
24 | int argc __unused, | 203 | int argc __unused, |
@@ -40,7 +219,7 @@ static int default_generate_script(const char *outfile __unused) | |||
40 | static struct scripting_ops default_scripting_ops = { | 219 | static struct scripting_ops default_scripting_ops = { |
41 | .start_script = default_start_script, | 220 | .start_script = default_start_script, |
42 | .stop_script = default_stop_script, | 221 | .stop_script = default_stop_script, |
43 | .process_event = print_event, | 222 | .process_event = process_event, |
44 | .generate_script = default_generate_script, | 223 | .generate_script = default_generate_script, |
45 | }; | 224 | }; |
46 | 225 | ||
@@ -75,26 +254,17 @@ static int process_sample_event(union perf_event *event, | |||
75 | return -1; | 254 | return -1; |
76 | } | 255 | } |
77 | 256 | ||
78 | if (session->sample_type & PERF_SAMPLE_RAW) { | 257 | if (debug_mode) { |
79 | if (debug_mode) { | 258 | if (sample->time < last_timestamp) { |
80 | if (sample->time < last_timestamp) { | 259 | pr_err("Samples misordered, previous: %" PRIu64 |
81 | pr_err("Samples misordered, previous: %" PRIu64 | 260 | " this: %" PRIu64 "\n", last_timestamp, |
82 | " this: %" PRIu64 "\n", last_timestamp, | 261 | sample->time); |
83 | sample->time); | 262 | nr_unordered++; |
84 | nr_unordered++; | ||
85 | } | ||
86 | last_timestamp = sample->time; | ||
87 | return 0; | ||
88 | } | 263 | } |
89 | /* | 264 | last_timestamp = sample->time; |
90 | * FIXME: better resolve from pid from the struct trace_entry | 265 | return 0; |
91 | * field, although it should be the same than this perf | ||
92 | * event pid | ||
93 | */ | ||
94 | scripting_ops->process_event(sample->cpu, sample->raw_data, | ||
95 | sample->raw_size, | ||
96 | sample->time, thread->comm); | ||
97 | } | 266 | } |
267 | scripting_ops->process_event(event, sample, session, thread); | ||
98 | 268 | ||
99 | session->hists.stats.total_period += sample->period; | 269 | session->hists.stats.total_period += sample->period; |
100 | return 0; | 270 | return 0; |
@@ -102,7 +272,10 @@ static int process_sample_event(union perf_event *event, | |||
102 | 272 | ||
103 | static struct perf_event_ops event_ops = { | 273 | static struct perf_event_ops event_ops = { |
104 | .sample = process_sample_event, | 274 | .sample = process_sample_event, |
275 | .mmap = perf_event__process_mmap, | ||
105 | .comm = perf_event__process_comm, | 276 | .comm = perf_event__process_comm, |
277 | .exit = perf_event__process_task, | ||
278 | .fork = perf_event__process_task, | ||
106 | .attr = perf_event__process_attr, | 279 | .attr = perf_event__process_attr, |
107 | .event_type = perf_event__process_event_type, | 280 | .event_type = perf_event__process_event_type, |
108 | .tracing_data = perf_event__process_tracing_data, | 281 | .tracing_data = perf_event__process_tracing_data, |
@@ -280,6 +453,68 @@ static int parse_scriptname(const struct option *opt __used, | |||
280 | return 0; | 453 | return 0; |
281 | } | 454 | } |
282 | 455 | ||
456 | static int parse_output_fields(const struct option *opt __used, | ||
457 | const char *arg, int unset __used) | ||
458 | { | ||
459 | char *tok; | ||
460 | int i, imax = sizeof(all_output_options) / sizeof(struct output_option); | ||
461 | int rc = 0; | ||
462 | char *str = strdup(arg); | ||
463 | int type = -1; | ||
464 | |||
465 | if (!str) | ||
466 | return -ENOMEM; | ||
467 | |||
468 | tok = strtok(str, ":"); | ||
469 | if (!tok) { | ||
470 | fprintf(stderr, | ||
471 | "Invalid field string - not prepended with type."); | ||
472 | return -EINVAL; | ||
473 | } | ||
474 | |||
475 | /* first word should state which event type user | ||
476 | * is specifying the fields | ||
477 | */ | ||
478 | if (!strcmp(tok, "hw")) | ||
479 | type = PERF_TYPE_HARDWARE; | ||
480 | else if (!strcmp(tok, "sw")) | ||
481 | type = PERF_TYPE_SOFTWARE; | ||
482 | else if (!strcmp(tok, "trace")) | ||
483 | type = PERF_TYPE_TRACEPOINT; | ||
484 | else { | ||
485 | fprintf(stderr, "Invalid event type in field string."); | ||
486 | return -EINVAL; | ||
487 | } | ||
488 | |||
489 | output_fields[type] = 0; | ||
490 | while (1) { | ||
491 | tok = strtok(NULL, ","); | ||
492 | if (!tok) | ||
493 | break; | ||
494 | for (i = 0; i < imax; ++i) { | ||
495 | if (strcmp(tok, all_output_options[i].str) == 0) { | ||
496 | output_fields[type] |= all_output_options[i].field; | ||
497 | break; | ||
498 | } | ||
499 | } | ||
500 | if (i == imax) { | ||
501 | fprintf(stderr, "Invalid field requested."); | ||
502 | rc = -EINVAL; | ||
503 | break; | ||
504 | } | ||
505 | } | ||
506 | |||
507 | if (output_fields[type] == 0) { | ||
508 | pr_debug("No fields requested for %s type. " | ||
509 | "Events will not be displayed\n", event_type(type)); | ||
510 | } | ||
511 | |||
512 | output_set_by_user = true; | ||
513 | |||
514 | free(str); | ||
515 | return rc; | ||
516 | } | ||
517 | |||
283 | /* Helper function for filesystems that return a dent->d_type DT_UNKNOWN */ | 518 | /* Helper function for filesystems that return a dent->d_type DT_UNKNOWN */ |
284 | static int is_directory(const char *base_path, const struct dirent *dent) | 519 | static int is_directory(const char *base_path, const struct dirent *dent) |
285 | { | 520 | { |
@@ -592,6 +827,17 @@ static const struct option options[] = { | |||
592 | "input file name"), | 827 | "input file name"), |
593 | OPT_BOOLEAN('d', "debug-mode", &debug_mode, | 828 | OPT_BOOLEAN('d', "debug-mode", &debug_mode, |
594 | "do various checks like samples ordering and lost events"), | 829 | "do various checks like samples ordering and lost events"), |
830 | OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, | ||
831 | "file", "vmlinux pathname"), | ||
832 | OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, | ||
833 | "file", "kallsyms pathname"), | ||
834 | OPT_BOOLEAN('G', "hide-call-graph", &no_callchain, | ||
835 | "When printing symbols do not display call chain"), | ||
836 | OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", | ||
837 | "Look for files with symbols relative to this directory"), | ||
838 | OPT_CALLBACK('f', "fields", NULL, "str", | ||
839 | "comma separated output fields prepend with 'type:'. Valid types: hw,sw,trace. Fields: comm,tid,pid,time,cpu,event,trace,sym", | ||
840 | parse_output_fields), | ||
595 | 841 | ||
596 | OPT_END() | 842 | OPT_END() |
597 | }; | 843 | }; |
@@ -772,14 +1018,22 @@ int cmd_script(int argc, const char **argv, const char *prefix __used) | |||
772 | if (session == NULL) | 1018 | if (session == NULL) |
773 | return -ENOMEM; | 1019 | return -ENOMEM; |
774 | 1020 | ||
775 | if (strcmp(input_name, "-") && | 1021 | if (!no_callchain) |
776 | !perf_session__has_traces(session, "record -R")) | 1022 | symbol_conf.use_callchain = true; |
777 | return -EINVAL; | 1023 | else |
1024 | symbol_conf.use_callchain = false; | ||
778 | 1025 | ||
779 | if (generate_script_lang) { | 1026 | if (generate_script_lang) { |
780 | struct stat perf_stat; | 1027 | struct stat perf_stat; |
1028 | int input; | ||
1029 | |||
1030 | if (output_set_by_user) { | ||
1031 | fprintf(stderr, | ||
1032 | "custom fields not supported for generated scripts"); | ||
1033 | return -1; | ||
1034 | } | ||
781 | 1035 | ||
782 | int input = open(input_name, O_RDONLY); | 1036 | input = open(input_name, O_RDONLY); |
783 | if (input < 0) { | 1037 | if (input < 0) { |
784 | perror("failed to open file"); | 1038 | perror("failed to open file"); |
785 | exit(-1); | 1039 | exit(-1); |