diff options
Diffstat (limited to 'tools/perf/builtin-script.c')
-rw-r--r-- | tools/perf/builtin-script.c | 1230 |
1 files changed, 1230 insertions, 0 deletions
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c new file mode 100644 index 000000000000..22747de7234b --- /dev/null +++ b/tools/perf/builtin-script.c | |||
@@ -0,0 +1,1230 @@ | |||
1 | #include "builtin.h" | ||
2 | |||
3 | #include "perf.h" | ||
4 | #include "util/cache.h" | ||
5 | #include "util/debug.h" | ||
6 | #include "util/exec_cmd.h" | ||
7 | #include "util/header.h" | ||
8 | #include "util/parse-options.h" | ||
9 | #include "util/session.h" | ||
10 | #include "util/symbol.h" | ||
11 | #include "util/thread.h" | ||
12 | #include "util/trace-event.h" | ||
13 | #include "util/util.h" | ||
14 | #include "util/evlist.h" | ||
15 | #include "util/evsel.h" | ||
16 | |||
17 | static char const *script_name; | ||
18 | static char const *generate_script_lang; | ||
19 | static bool debug_mode; | ||
20 | static u64 last_timestamp; | ||
21 | static u64 nr_unordered; | ||
22 | extern const struct option record_options[]; | ||
23 | static bool no_callchain; | ||
24 | |||
25 | enum perf_output_field { | ||
26 | PERF_OUTPUT_COMM = 1U << 0, | ||
27 | PERF_OUTPUT_TID = 1U << 1, | ||
28 | PERF_OUTPUT_PID = 1U << 2, | ||
29 | PERF_OUTPUT_TIME = 1U << 3, | ||
30 | PERF_OUTPUT_CPU = 1U << 4, | ||
31 | PERF_OUTPUT_EVNAME = 1U << 5, | ||
32 | PERF_OUTPUT_TRACE = 1U << 6, | ||
33 | PERF_OUTPUT_SYM = 1U << 7, | ||
34 | }; | ||
35 | |||
36 | struct output_option { | ||
37 | const char *str; | ||
38 | enum perf_output_field field; | ||
39 | } all_output_options[] = { | ||
40 | {.str = "comm", .field = PERF_OUTPUT_COMM}, | ||
41 | {.str = "tid", .field = PERF_OUTPUT_TID}, | ||
42 | {.str = "pid", .field = PERF_OUTPUT_PID}, | ||
43 | {.str = "time", .field = PERF_OUTPUT_TIME}, | ||
44 | {.str = "cpu", .field = PERF_OUTPUT_CPU}, | ||
45 | {.str = "event", .field = PERF_OUTPUT_EVNAME}, | ||
46 | {.str = "trace", .field = PERF_OUTPUT_TRACE}, | ||
47 | {.str = "sym", .field = PERF_OUTPUT_SYM}, | ||
48 | }; | ||
49 | |||
50 | /* default set to maintain compatibility with current format */ | ||
51 | static struct { | ||
52 | bool user_set; | ||
53 | bool wildcard_set; | ||
54 | u64 fields; | ||
55 | u64 invalid_fields; | ||
56 | } output[PERF_TYPE_MAX] = { | ||
57 | |||
58 | [PERF_TYPE_HARDWARE] = { | ||
59 | .user_set = false, | ||
60 | |||
61 | .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID | | ||
62 | PERF_OUTPUT_CPU | PERF_OUTPUT_TIME | | ||
63 | PERF_OUTPUT_EVNAME | PERF_OUTPUT_SYM, | ||
64 | |||
65 | .invalid_fields = PERF_OUTPUT_TRACE, | ||
66 | }, | ||
67 | |||
68 | [PERF_TYPE_SOFTWARE] = { | ||
69 | .user_set = false, | ||
70 | |||
71 | .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID | | ||
72 | PERF_OUTPUT_CPU | PERF_OUTPUT_TIME | | ||
73 | PERF_OUTPUT_EVNAME | PERF_OUTPUT_SYM, | ||
74 | |||
75 | .invalid_fields = PERF_OUTPUT_TRACE, | ||
76 | }, | ||
77 | |||
78 | [PERF_TYPE_TRACEPOINT] = { | ||
79 | .user_set = false, | ||
80 | |||
81 | .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID | | ||
82 | PERF_OUTPUT_CPU | PERF_OUTPUT_TIME | | ||
83 | PERF_OUTPUT_EVNAME | PERF_OUTPUT_TRACE, | ||
84 | }, | ||
85 | |||
86 | [PERF_TYPE_RAW] = { | ||
87 | .user_set = false, | ||
88 | |||
89 | .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID | | ||
90 | PERF_OUTPUT_CPU | PERF_OUTPUT_TIME | | ||
91 | PERF_OUTPUT_EVNAME | PERF_OUTPUT_SYM, | ||
92 | |||
93 | .invalid_fields = PERF_OUTPUT_TRACE, | ||
94 | }, | ||
95 | }; | ||
96 | |||
97 | static bool output_set_by_user(void) | ||
98 | { | ||
99 | int j; | ||
100 | for (j = 0; j < PERF_TYPE_MAX; ++j) { | ||
101 | if (output[j].user_set) | ||
102 | return true; | ||
103 | } | ||
104 | return false; | ||
105 | } | ||
106 | |||
107 | static const char *output_field2str(enum perf_output_field field) | ||
108 | { | ||
109 | int i, imax = ARRAY_SIZE(all_output_options); | ||
110 | const char *str = ""; | ||
111 | |||
112 | for (i = 0; i < imax; ++i) { | ||
113 | if (all_output_options[i].field == field) { | ||
114 | str = all_output_options[i].str; | ||
115 | break; | ||
116 | } | ||
117 | } | ||
118 | return str; | ||
119 | } | ||
120 | |||
121 | #define PRINT_FIELD(x) (output[attr->type].fields & PERF_OUTPUT_##x) | ||
122 | |||
123 | static int perf_event_attr__check_stype(struct perf_event_attr *attr, | ||
124 | u64 sample_type, const char *sample_msg, | ||
125 | enum perf_output_field field) | ||
126 | { | ||
127 | int type = attr->type; | ||
128 | const char *evname; | ||
129 | |||
130 | if (attr->sample_type & sample_type) | ||
131 | return 0; | ||
132 | |||
133 | if (output[type].user_set) { | ||
134 | evname = __event_name(attr->type, attr->config); | ||
135 | pr_err("Samples for '%s' event do not have %s attribute set. " | ||
136 | "Cannot print '%s' field.\n", | ||
137 | evname, sample_msg, output_field2str(field)); | ||
138 | return -1; | ||
139 | } | ||
140 | |||
141 | /* user did not ask for it explicitly so remove from the default list */ | ||
142 | output[type].fields &= ~field; | ||
143 | evname = __event_name(attr->type, attr->config); | ||
144 | pr_debug("Samples for '%s' event do not have %s attribute set. " | ||
145 | "Skipping '%s' field.\n", | ||
146 | evname, sample_msg, output_field2str(field)); | ||
147 | |||
148 | return 0; | ||
149 | } | ||
150 | |||
151 | static int perf_evsel__check_attr(struct perf_evsel *evsel, | ||
152 | struct perf_session *session) | ||
153 | { | ||
154 | struct perf_event_attr *attr = &evsel->attr; | ||
155 | |||
156 | if (PRINT_FIELD(TRACE) && | ||
157 | !perf_session__has_traces(session, "record -R")) | ||
158 | return -EINVAL; | ||
159 | |||
160 | if (PRINT_FIELD(SYM)) { | ||
161 | if (perf_event_attr__check_stype(attr, PERF_SAMPLE_IP, "IP", | ||
162 | PERF_OUTPUT_SYM)) | ||
163 | return -EINVAL; | ||
164 | |||
165 | if (!no_callchain && | ||
166 | !(attr->sample_type & PERF_SAMPLE_CALLCHAIN)) | ||
167 | symbol_conf.use_callchain = false; | ||
168 | } | ||
169 | |||
170 | if ((PRINT_FIELD(PID) || PRINT_FIELD(TID)) && | ||
171 | perf_event_attr__check_stype(attr, PERF_SAMPLE_TID, "TID", | ||
172 | PERF_OUTPUT_TID|PERF_OUTPUT_PID)) | ||
173 | return -EINVAL; | ||
174 | |||
175 | if (PRINT_FIELD(TIME) && | ||
176 | perf_event_attr__check_stype(attr, PERF_SAMPLE_TIME, "TIME", | ||
177 | PERF_OUTPUT_TIME)) | ||
178 | return -EINVAL; | ||
179 | |||
180 | if (PRINT_FIELD(CPU) && | ||
181 | perf_event_attr__check_stype(attr, PERF_SAMPLE_CPU, "CPU", | ||
182 | PERF_OUTPUT_CPU)) | ||
183 | return -EINVAL; | ||
184 | |||
185 | return 0; | ||
186 | } | ||
187 | |||
188 | /* | ||
189 | * verify all user requested events exist and the samples | ||
190 | * have the expected data | ||
191 | */ | ||
192 | static int perf_session__check_output_opt(struct perf_session *session) | ||
193 | { | ||
194 | int j; | ||
195 | struct perf_evsel *evsel; | ||
196 | |||
197 | for (j = 0; j < PERF_TYPE_MAX; ++j) { | ||
198 | evsel = perf_session__find_first_evtype(session, j); | ||
199 | |||
200 | /* | ||
201 | * even if fields is set to 0 (ie., show nothing) event must | ||
202 | * exist if user explicitly includes it on the command line | ||
203 | */ | ||
204 | if (!evsel && output[j].user_set && !output[j].wildcard_set) { | ||
205 | pr_err("%s events do not exist. " | ||
206 | "Remove corresponding -f option to proceed.\n", | ||
207 | event_type(j)); | ||
208 | return -1; | ||
209 | } | ||
210 | |||
211 | if (evsel && output[j].fields && | ||
212 | perf_evsel__check_attr(evsel, session)) | ||
213 | return -1; | ||
214 | } | ||
215 | |||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | static void print_sample_start(struct perf_sample *sample, | ||
220 | struct thread *thread, | ||
221 | struct perf_event_attr *attr) | ||
222 | { | ||
223 | int type; | ||
224 | struct event *event; | ||
225 | const char *evname = NULL; | ||
226 | unsigned long secs; | ||
227 | unsigned long usecs; | ||
228 | unsigned long long nsecs; | ||
229 | |||
230 | if (PRINT_FIELD(COMM)) { | ||
231 | if (latency_format) | ||
232 | printf("%8.8s ", thread->comm); | ||
233 | else if (PRINT_FIELD(SYM) && symbol_conf.use_callchain) | ||
234 | printf("%s ", thread->comm); | ||
235 | else | ||
236 | printf("%16s ", thread->comm); | ||
237 | } | ||
238 | |||
239 | if (PRINT_FIELD(PID) && PRINT_FIELD(TID)) | ||
240 | printf("%5d/%-5d ", sample->pid, sample->tid); | ||
241 | else if (PRINT_FIELD(PID)) | ||
242 | printf("%5d ", sample->pid); | ||
243 | else if (PRINT_FIELD(TID)) | ||
244 | printf("%5d ", sample->tid); | ||
245 | |||
246 | if (PRINT_FIELD(CPU)) { | ||
247 | if (latency_format) | ||
248 | printf("%3d ", sample->cpu); | ||
249 | else | ||
250 | printf("[%03d] ", sample->cpu); | ||
251 | } | ||
252 | |||
253 | if (PRINT_FIELD(TIME)) { | ||
254 | nsecs = sample->time; | ||
255 | secs = nsecs / NSECS_PER_SEC; | ||
256 | nsecs -= secs * NSECS_PER_SEC; | ||
257 | usecs = nsecs / NSECS_PER_USEC; | ||
258 | printf("%5lu.%06lu: ", secs, usecs); | ||
259 | } | ||
260 | |||
261 | if (PRINT_FIELD(EVNAME)) { | ||
262 | if (attr->type == PERF_TYPE_TRACEPOINT) { | ||
263 | type = trace_parse_common_type(sample->raw_data); | ||
264 | event = trace_find_event(type); | ||
265 | if (event) | ||
266 | evname = event->name; | ||
267 | } else | ||
268 | evname = __event_name(attr->type, attr->config); | ||
269 | |||
270 | printf("%s: ", evname ? evname : "(unknown)"); | ||
271 | } | ||
272 | } | ||
273 | |||
274 | static void process_event(union perf_event *event __unused, | ||
275 | struct perf_sample *sample, | ||
276 | struct perf_evsel *evsel, | ||
277 | struct perf_session *session, | ||
278 | struct thread *thread) | ||
279 | { | ||
280 | struct perf_event_attr *attr = &evsel->attr; | ||
281 | |||
282 | if (output[attr->type].fields == 0) | ||
283 | return; | ||
284 | |||
285 | print_sample_start(sample, thread, attr); | ||
286 | |||
287 | if (PRINT_FIELD(TRACE)) | ||
288 | print_trace_event(sample->cpu, sample->raw_data, | ||
289 | sample->raw_size); | ||
290 | |||
291 | if (PRINT_FIELD(SYM)) { | ||
292 | if (!symbol_conf.use_callchain) | ||
293 | printf(" "); | ||
294 | else | ||
295 | printf("\n"); | ||
296 | perf_session__print_symbols(event, sample, session); | ||
297 | } | ||
298 | |||
299 | printf("\n"); | ||
300 | } | ||
301 | |||
302 | static int default_start_script(const char *script __unused, | ||
303 | int argc __unused, | ||
304 | const char **argv __unused) | ||
305 | { | ||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static int default_stop_script(void) | ||
310 | { | ||
311 | return 0; | ||
312 | } | ||
313 | |||
314 | static int default_generate_script(const char *outfile __unused) | ||
315 | { | ||
316 | return 0; | ||
317 | } | ||
318 | |||
319 | static struct scripting_ops default_scripting_ops = { | ||
320 | .start_script = default_start_script, | ||
321 | .stop_script = default_stop_script, | ||
322 | .process_event = process_event, | ||
323 | .generate_script = default_generate_script, | ||
324 | }; | ||
325 | |||
326 | static struct scripting_ops *scripting_ops; | ||
327 | |||
328 | static void setup_scripting(void) | ||
329 | { | ||
330 | setup_perl_scripting(); | ||
331 | setup_python_scripting(); | ||
332 | |||
333 | scripting_ops = &default_scripting_ops; | ||
334 | } | ||
335 | |||
336 | static int cleanup_scripting(void) | ||
337 | { | ||
338 | pr_debug("\nperf script stopped\n"); | ||
339 | |||
340 | return scripting_ops->stop_script(); | ||
341 | } | ||
342 | |||
343 | static char const *input_name = "perf.data"; | ||
344 | |||
345 | static int process_sample_event(union perf_event *event, | ||
346 | struct perf_sample *sample, | ||
347 | struct perf_evsel *evsel, | ||
348 | struct perf_session *session) | ||
349 | { | ||
350 | struct thread *thread = perf_session__findnew(session, event->ip.pid); | ||
351 | |||
352 | if (thread == NULL) { | ||
353 | pr_debug("problem processing %d event, skipping it.\n", | ||
354 | event->header.type); | ||
355 | return -1; | ||
356 | } | ||
357 | |||
358 | if (debug_mode) { | ||
359 | if (sample->time < last_timestamp) { | ||
360 | pr_err("Samples misordered, previous: %" PRIu64 | ||
361 | " this: %" PRIu64 "\n", last_timestamp, | ||
362 | sample->time); | ||
363 | nr_unordered++; | ||
364 | } | ||
365 | last_timestamp = sample->time; | ||
366 | return 0; | ||
367 | } | ||
368 | scripting_ops->process_event(event, sample, evsel, session, thread); | ||
369 | |||
370 | session->hists.stats.total_period += sample->period; | ||
371 | return 0; | ||
372 | } | ||
373 | |||
374 | static struct perf_event_ops event_ops = { | ||
375 | .sample = process_sample_event, | ||
376 | .mmap = perf_event__process_mmap, | ||
377 | .comm = perf_event__process_comm, | ||
378 | .exit = perf_event__process_task, | ||
379 | .fork = perf_event__process_task, | ||
380 | .attr = perf_event__process_attr, | ||
381 | .event_type = perf_event__process_event_type, | ||
382 | .tracing_data = perf_event__process_tracing_data, | ||
383 | .build_id = perf_event__process_build_id, | ||
384 | .ordered_samples = true, | ||
385 | .ordering_requires_timestamps = true, | ||
386 | }; | ||
387 | |||
388 | extern volatile int session_done; | ||
389 | |||
390 | static void sig_handler(int sig __unused) | ||
391 | { | ||
392 | session_done = 1; | ||
393 | } | ||
394 | |||
395 | static int __cmd_script(struct perf_session *session) | ||
396 | { | ||
397 | int ret; | ||
398 | |||
399 | signal(SIGINT, sig_handler); | ||
400 | |||
401 | ret = perf_session__process_events(session, &event_ops); | ||
402 | |||
403 | if (debug_mode) | ||
404 | pr_err("Misordered timestamps: %" PRIu64 "\n", nr_unordered); | ||
405 | |||
406 | return ret; | ||
407 | } | ||
408 | |||
409 | struct script_spec { | ||
410 | struct list_head node; | ||
411 | struct scripting_ops *ops; | ||
412 | char spec[0]; | ||
413 | }; | ||
414 | |||
415 | static LIST_HEAD(script_specs); | ||
416 | |||
417 | static struct script_spec *script_spec__new(const char *spec, | ||
418 | struct scripting_ops *ops) | ||
419 | { | ||
420 | struct script_spec *s = malloc(sizeof(*s) + strlen(spec) + 1); | ||
421 | |||
422 | if (s != NULL) { | ||
423 | strcpy(s->spec, spec); | ||
424 | s->ops = ops; | ||
425 | } | ||
426 | |||
427 | return s; | ||
428 | } | ||
429 | |||
430 | static void script_spec__delete(struct script_spec *s) | ||
431 | { | ||
432 | free(s->spec); | ||
433 | free(s); | ||
434 | } | ||
435 | |||
436 | static void script_spec__add(struct script_spec *s) | ||
437 | { | ||
438 | list_add_tail(&s->node, &script_specs); | ||
439 | } | ||
440 | |||
441 | static struct script_spec *script_spec__find(const char *spec) | ||
442 | { | ||
443 | struct script_spec *s; | ||
444 | |||
445 | list_for_each_entry(s, &script_specs, node) | ||
446 | if (strcasecmp(s->spec, spec) == 0) | ||
447 | return s; | ||
448 | return NULL; | ||
449 | } | ||
450 | |||
451 | static struct script_spec *script_spec__findnew(const char *spec, | ||
452 | struct scripting_ops *ops) | ||
453 | { | ||
454 | struct script_spec *s = script_spec__find(spec); | ||
455 | |||
456 | if (s) | ||
457 | return s; | ||
458 | |||
459 | s = script_spec__new(spec, ops); | ||
460 | if (!s) | ||
461 | goto out_delete_spec; | ||
462 | |||
463 | script_spec__add(s); | ||
464 | |||
465 | return s; | ||
466 | |||
467 | out_delete_spec: | ||
468 | script_spec__delete(s); | ||
469 | |||
470 | return NULL; | ||
471 | } | ||
472 | |||
473 | int script_spec_register(const char *spec, struct scripting_ops *ops) | ||
474 | { | ||
475 | struct script_spec *s; | ||
476 | |||
477 | s = script_spec__find(spec); | ||
478 | if (s) | ||
479 | return -1; | ||
480 | |||
481 | s = script_spec__findnew(spec, ops); | ||
482 | if (!s) | ||
483 | return -1; | ||
484 | |||
485 | return 0; | ||
486 | } | ||
487 | |||
488 | static struct scripting_ops *script_spec__lookup(const char *spec) | ||
489 | { | ||
490 | struct script_spec *s = script_spec__find(spec); | ||
491 | if (!s) | ||
492 | return NULL; | ||
493 | |||
494 | return s->ops; | ||
495 | } | ||
496 | |||
497 | static void list_available_languages(void) | ||
498 | { | ||
499 | struct script_spec *s; | ||
500 | |||
501 | fprintf(stderr, "\n"); | ||
502 | fprintf(stderr, "Scripting language extensions (used in " | ||
503 | "perf script -s [spec:]script.[spec]):\n\n"); | ||
504 | |||
505 | list_for_each_entry(s, &script_specs, node) | ||
506 | fprintf(stderr, " %-42s [%s]\n", s->spec, s->ops->name); | ||
507 | |||
508 | fprintf(stderr, "\n"); | ||
509 | } | ||
510 | |||
511 | static int parse_scriptname(const struct option *opt __used, | ||
512 | const char *str, int unset __used) | ||
513 | { | ||
514 | char spec[PATH_MAX]; | ||
515 | const char *script, *ext; | ||
516 | int len; | ||
517 | |||
518 | if (strcmp(str, "lang") == 0) { | ||
519 | list_available_languages(); | ||
520 | exit(0); | ||
521 | } | ||
522 | |||
523 | script = strchr(str, ':'); | ||
524 | if (script) { | ||
525 | len = script - str; | ||
526 | if (len >= PATH_MAX) { | ||
527 | fprintf(stderr, "invalid language specifier"); | ||
528 | return -1; | ||
529 | } | ||
530 | strncpy(spec, str, len); | ||
531 | spec[len] = '\0'; | ||
532 | scripting_ops = script_spec__lookup(spec); | ||
533 | if (!scripting_ops) { | ||
534 | fprintf(stderr, "invalid language specifier"); | ||
535 | return -1; | ||
536 | } | ||
537 | script++; | ||
538 | } else { | ||
539 | script = str; | ||
540 | ext = strrchr(script, '.'); | ||
541 | if (!ext) { | ||
542 | fprintf(stderr, "invalid script extension"); | ||
543 | return -1; | ||
544 | } | ||
545 | scripting_ops = script_spec__lookup(++ext); | ||
546 | if (!scripting_ops) { | ||
547 | fprintf(stderr, "invalid script extension"); | ||
548 | return -1; | ||
549 | } | ||
550 | } | ||
551 | |||
552 | script_name = strdup(script); | ||
553 | |||
554 | return 0; | ||
555 | } | ||
556 | |||
557 | static int parse_output_fields(const struct option *opt __used, | ||
558 | const char *arg, int unset __used) | ||
559 | { | ||
560 | char *tok; | ||
561 | int i, imax = sizeof(all_output_options) / sizeof(struct output_option); | ||
562 | int j; | ||
563 | int rc = 0; | ||
564 | char *str = strdup(arg); | ||
565 | int type = -1; | ||
566 | |||
567 | if (!str) | ||
568 | return -ENOMEM; | ||
569 | |||
570 | /* first word can state for which event type the user is specifying | ||
571 | * the fields. If no type exists, the specified fields apply to all | ||
572 | * event types found in the file minus the invalid fields for a type. | ||
573 | */ | ||
574 | tok = strchr(str, ':'); | ||
575 | if (tok) { | ||
576 | *tok = '\0'; | ||
577 | tok++; | ||
578 | if (!strcmp(str, "hw")) | ||
579 | type = PERF_TYPE_HARDWARE; | ||
580 | else if (!strcmp(str, "sw")) | ||
581 | type = PERF_TYPE_SOFTWARE; | ||
582 | else if (!strcmp(str, "trace")) | ||
583 | type = PERF_TYPE_TRACEPOINT; | ||
584 | else if (!strcmp(str, "raw")) | ||
585 | type = PERF_TYPE_RAW; | ||
586 | else { | ||
587 | fprintf(stderr, "Invalid event type in field string.\n"); | ||
588 | return -EINVAL; | ||
589 | } | ||
590 | |||
591 | if (output[type].user_set) | ||
592 | pr_warning("Overriding previous field request for %s events.\n", | ||
593 | event_type(type)); | ||
594 | |||
595 | output[type].fields = 0; | ||
596 | output[type].user_set = true; | ||
597 | output[type].wildcard_set = false; | ||
598 | |||
599 | } else { | ||
600 | tok = str; | ||
601 | if (strlen(str) == 0) { | ||
602 | fprintf(stderr, | ||
603 | "Cannot set fields to 'none' for all event types.\n"); | ||
604 | rc = -EINVAL; | ||
605 | goto out; | ||
606 | } | ||
607 | |||
608 | if (output_set_by_user()) | ||
609 | pr_warning("Overriding previous field request for all events.\n"); | ||
610 | |||
611 | for (j = 0; j < PERF_TYPE_MAX; ++j) { | ||
612 | output[j].fields = 0; | ||
613 | output[j].user_set = true; | ||
614 | output[j].wildcard_set = true; | ||
615 | } | ||
616 | } | ||
617 | |||
618 | tok = strtok(tok, ","); | ||
619 | while (tok) { | ||
620 | for (i = 0; i < imax; ++i) { | ||
621 | if (strcmp(tok, all_output_options[i].str) == 0) | ||
622 | break; | ||
623 | } | ||
624 | if (i == imax) { | ||
625 | fprintf(stderr, "Invalid field requested.\n"); | ||
626 | rc = -EINVAL; | ||
627 | goto out; | ||
628 | } | ||
629 | |||
630 | if (type == -1) { | ||
631 | /* add user option to all events types for | ||
632 | * which it is valid | ||
633 | */ | ||
634 | for (j = 0; j < PERF_TYPE_MAX; ++j) { | ||
635 | if (output[j].invalid_fields & all_output_options[i].field) { | ||
636 | pr_warning("\'%s\' not valid for %s events. Ignoring.\n", | ||
637 | all_output_options[i].str, event_type(j)); | ||
638 | } else | ||
639 | output[j].fields |= all_output_options[i].field; | ||
640 | } | ||
641 | } else { | ||
642 | if (output[type].invalid_fields & all_output_options[i].field) { | ||
643 | fprintf(stderr, "\'%s\' not valid for %s events.\n", | ||
644 | all_output_options[i].str, event_type(type)); | ||
645 | |||
646 | rc = -EINVAL; | ||
647 | goto out; | ||
648 | } | ||
649 | output[type].fields |= all_output_options[i].field; | ||
650 | } | ||
651 | |||
652 | tok = strtok(NULL, ","); | ||
653 | } | ||
654 | |||
655 | if (type >= 0) { | ||
656 | if (output[type].fields == 0) { | ||
657 | pr_debug("No fields requested for %s type. " | ||
658 | "Events will not be displayed.\n", event_type(type)); | ||
659 | } | ||
660 | } | ||
661 | |||
662 | out: | ||
663 | free(str); | ||
664 | return rc; | ||
665 | } | ||
666 | |||
667 | /* Helper function for filesystems that return a dent->d_type DT_UNKNOWN */ | ||
668 | static int is_directory(const char *base_path, const struct dirent *dent) | ||
669 | { | ||
670 | char path[PATH_MAX]; | ||
671 | struct stat st; | ||
672 | |||
673 | sprintf(path, "%s/%s", base_path, dent->d_name); | ||
674 | if (stat(path, &st)) | ||
675 | return 0; | ||
676 | |||
677 | return S_ISDIR(st.st_mode); | ||
678 | } | ||
679 | |||
680 | #define for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next)\ | ||
681 | while (!readdir_r(scripts_dir, &lang_dirent, &lang_next) && \ | ||
682 | lang_next) \ | ||
683 | if ((lang_dirent.d_type == DT_DIR || \ | ||
684 | (lang_dirent.d_type == DT_UNKNOWN && \ | ||
685 | is_directory(scripts_path, &lang_dirent))) && \ | ||
686 | (strcmp(lang_dirent.d_name, ".")) && \ | ||
687 | (strcmp(lang_dirent.d_name, ".."))) | ||
688 | |||
689 | #define for_each_script(lang_path, lang_dir, script_dirent, script_next)\ | ||
690 | while (!readdir_r(lang_dir, &script_dirent, &script_next) && \ | ||
691 | script_next) \ | ||
692 | if (script_dirent.d_type != DT_DIR && \ | ||
693 | (script_dirent.d_type != DT_UNKNOWN || \ | ||
694 | !is_directory(lang_path, &script_dirent))) | ||
695 | |||
696 | |||
697 | #define RECORD_SUFFIX "-record" | ||
698 | #define REPORT_SUFFIX "-report" | ||
699 | |||
700 | struct script_desc { | ||
701 | struct list_head node; | ||
702 | char *name; | ||
703 | char *half_liner; | ||
704 | char *args; | ||
705 | }; | ||
706 | |||
707 | static LIST_HEAD(script_descs); | ||
708 | |||
709 | static struct script_desc *script_desc__new(const char *name) | ||
710 | { | ||
711 | struct script_desc *s = zalloc(sizeof(*s)); | ||
712 | |||
713 | if (s != NULL && name) | ||
714 | s->name = strdup(name); | ||
715 | |||
716 | return s; | ||
717 | } | ||
718 | |||
719 | static void script_desc__delete(struct script_desc *s) | ||
720 | { | ||
721 | free(s->name); | ||
722 | free(s->half_liner); | ||
723 | free(s->args); | ||
724 | free(s); | ||
725 | } | ||
726 | |||
727 | static void script_desc__add(struct script_desc *s) | ||
728 | { | ||
729 | list_add_tail(&s->node, &script_descs); | ||
730 | } | ||
731 | |||
732 | static struct script_desc *script_desc__find(const char *name) | ||
733 | { | ||
734 | struct script_desc *s; | ||
735 | |||
736 | list_for_each_entry(s, &script_descs, node) | ||
737 | if (strcasecmp(s->name, name) == 0) | ||
738 | return s; | ||
739 | return NULL; | ||
740 | } | ||
741 | |||
742 | static struct script_desc *script_desc__findnew(const char *name) | ||
743 | { | ||
744 | struct script_desc *s = script_desc__find(name); | ||
745 | |||
746 | if (s) | ||
747 | return s; | ||
748 | |||
749 | s = script_desc__new(name); | ||
750 | if (!s) | ||
751 | goto out_delete_desc; | ||
752 | |||
753 | script_desc__add(s); | ||
754 | |||
755 | return s; | ||
756 | |||
757 | out_delete_desc: | ||
758 | script_desc__delete(s); | ||
759 | |||
760 | return NULL; | ||
761 | } | ||
762 | |||
763 | static const char *ends_with(const char *str, const char *suffix) | ||
764 | { | ||
765 | size_t suffix_len = strlen(suffix); | ||
766 | const char *p = str; | ||
767 | |||
768 | if (strlen(str) > suffix_len) { | ||
769 | p = str + strlen(str) - suffix_len; | ||
770 | if (!strncmp(p, suffix, suffix_len)) | ||
771 | return p; | ||
772 | } | ||
773 | |||
774 | return NULL; | ||
775 | } | ||
776 | |||
777 | static char *ltrim(char *str) | ||
778 | { | ||
779 | int len = strlen(str); | ||
780 | |||
781 | while (len && isspace(*str)) { | ||
782 | len--; | ||
783 | str++; | ||
784 | } | ||
785 | |||
786 | return str; | ||
787 | } | ||
788 | |||
789 | static int read_script_info(struct script_desc *desc, const char *filename) | ||
790 | { | ||
791 | char line[BUFSIZ], *p; | ||
792 | FILE *fp; | ||
793 | |||
794 | fp = fopen(filename, "r"); | ||
795 | if (!fp) | ||
796 | return -1; | ||
797 | |||
798 | while (fgets(line, sizeof(line), fp)) { | ||
799 | p = ltrim(line); | ||
800 | if (strlen(p) == 0) | ||
801 | continue; | ||
802 | if (*p != '#') | ||
803 | continue; | ||
804 | p++; | ||
805 | if (strlen(p) && *p == '!') | ||
806 | continue; | ||
807 | |||
808 | p = ltrim(p); | ||
809 | if (strlen(p) && p[strlen(p) - 1] == '\n') | ||
810 | p[strlen(p) - 1] = '\0'; | ||
811 | |||
812 | if (!strncmp(p, "description:", strlen("description:"))) { | ||
813 | p += strlen("description:"); | ||
814 | desc->half_liner = strdup(ltrim(p)); | ||
815 | continue; | ||
816 | } | ||
817 | |||
818 | if (!strncmp(p, "args:", strlen("args:"))) { | ||
819 | p += strlen("args:"); | ||
820 | desc->args = strdup(ltrim(p)); | ||
821 | continue; | ||
822 | } | ||
823 | } | ||
824 | |||
825 | fclose(fp); | ||
826 | |||
827 | return 0; | ||
828 | } | ||
829 | |||
830 | static int list_available_scripts(const struct option *opt __used, | ||
831 | const char *s __used, int unset __used) | ||
832 | { | ||
833 | struct dirent *script_next, *lang_next, script_dirent, lang_dirent; | ||
834 | char scripts_path[MAXPATHLEN]; | ||
835 | DIR *scripts_dir, *lang_dir; | ||
836 | char script_path[MAXPATHLEN]; | ||
837 | char lang_path[MAXPATHLEN]; | ||
838 | struct script_desc *desc; | ||
839 | char first_half[BUFSIZ]; | ||
840 | char *script_root; | ||
841 | char *str; | ||
842 | |||
843 | snprintf(scripts_path, MAXPATHLEN, "%s/scripts", perf_exec_path()); | ||
844 | |||
845 | scripts_dir = opendir(scripts_path); | ||
846 | if (!scripts_dir) | ||
847 | return -1; | ||
848 | |||
849 | for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next) { | ||
850 | snprintf(lang_path, MAXPATHLEN, "%s/%s/bin", scripts_path, | ||
851 | lang_dirent.d_name); | ||
852 | lang_dir = opendir(lang_path); | ||
853 | if (!lang_dir) | ||
854 | continue; | ||
855 | |||
856 | for_each_script(lang_path, lang_dir, script_dirent, script_next) { | ||
857 | script_root = strdup(script_dirent.d_name); | ||
858 | str = (char *)ends_with(script_root, REPORT_SUFFIX); | ||
859 | if (str) { | ||
860 | *str = '\0'; | ||
861 | desc = script_desc__findnew(script_root); | ||
862 | snprintf(script_path, MAXPATHLEN, "%s/%s", | ||
863 | lang_path, script_dirent.d_name); | ||
864 | read_script_info(desc, script_path); | ||
865 | } | ||
866 | free(script_root); | ||
867 | } | ||
868 | } | ||
869 | |||
870 | fprintf(stdout, "List of available trace scripts:\n"); | ||
871 | list_for_each_entry(desc, &script_descs, node) { | ||
872 | sprintf(first_half, "%s %s", desc->name, | ||
873 | desc->args ? desc->args : ""); | ||
874 | fprintf(stdout, " %-36s %s\n", first_half, | ||
875 | desc->half_liner ? desc->half_liner : ""); | ||
876 | } | ||
877 | |||
878 | exit(0); | ||
879 | } | ||
880 | |||
881 | static char *get_script_path(const char *script_root, const char *suffix) | ||
882 | { | ||
883 | struct dirent *script_next, *lang_next, script_dirent, lang_dirent; | ||
884 | char scripts_path[MAXPATHLEN]; | ||
885 | char script_path[MAXPATHLEN]; | ||
886 | DIR *scripts_dir, *lang_dir; | ||
887 | char lang_path[MAXPATHLEN]; | ||
888 | char *str, *__script_root; | ||
889 | char *path = NULL; | ||
890 | |||
891 | snprintf(scripts_path, MAXPATHLEN, "%s/scripts", perf_exec_path()); | ||
892 | |||
893 | scripts_dir = opendir(scripts_path); | ||
894 | if (!scripts_dir) | ||
895 | return NULL; | ||
896 | |||
897 | for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next) { | ||
898 | snprintf(lang_path, MAXPATHLEN, "%s/%s/bin", scripts_path, | ||
899 | lang_dirent.d_name); | ||
900 | lang_dir = opendir(lang_path); | ||
901 | if (!lang_dir) | ||
902 | continue; | ||
903 | |||
904 | for_each_script(lang_path, lang_dir, script_dirent, script_next) { | ||
905 | __script_root = strdup(script_dirent.d_name); | ||
906 | str = (char *)ends_with(__script_root, suffix); | ||
907 | if (str) { | ||
908 | *str = '\0'; | ||
909 | if (strcmp(__script_root, script_root)) | ||
910 | continue; | ||
911 | snprintf(script_path, MAXPATHLEN, "%s/%s", | ||
912 | lang_path, script_dirent.d_name); | ||
913 | path = strdup(script_path); | ||
914 | free(__script_root); | ||
915 | break; | ||
916 | } | ||
917 | free(__script_root); | ||
918 | } | ||
919 | } | ||
920 | |||
921 | return path; | ||
922 | } | ||
923 | |||
924 | static bool is_top_script(const char *script_path) | ||
925 | { | ||
926 | return ends_with(script_path, "top") == NULL ? false : true; | ||
927 | } | ||
928 | |||
929 | static int has_required_arg(char *script_path) | ||
930 | { | ||
931 | struct script_desc *desc; | ||
932 | int n_args = 0; | ||
933 | char *p; | ||
934 | |||
935 | desc = script_desc__new(NULL); | ||
936 | |||
937 | if (read_script_info(desc, script_path)) | ||
938 | goto out; | ||
939 | |||
940 | if (!desc->args) | ||
941 | goto out; | ||
942 | |||
943 | for (p = desc->args; *p; p++) | ||
944 | if (*p == '<') | ||
945 | n_args++; | ||
946 | out: | ||
947 | script_desc__delete(desc); | ||
948 | |||
949 | return n_args; | ||
950 | } | ||
951 | |||
952 | static const char * const script_usage[] = { | ||
953 | "perf script [<options>]", | ||
954 | "perf script [<options>] record <script> [<record-options>] <command>", | ||
955 | "perf script [<options>] report <script> [script-args]", | ||
956 | "perf script [<options>] <script> [<record-options>] <command>", | ||
957 | "perf script [<options>] <top-script> [script-args]", | ||
958 | NULL | ||
959 | }; | ||
960 | |||
961 | static const struct option options[] = { | ||
962 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, | ||
963 | "dump raw trace in ASCII"), | ||
964 | OPT_INCR('v', "verbose", &verbose, | ||
965 | "be more verbose (show symbol address, etc)"), | ||
966 | OPT_BOOLEAN('L', "Latency", &latency_format, | ||
967 | "show latency attributes (irqs/preemption disabled, etc)"), | ||
968 | OPT_CALLBACK_NOOPT('l', "list", NULL, NULL, "list available scripts", | ||
969 | list_available_scripts), | ||
970 | OPT_CALLBACK('s', "script", NULL, "name", | ||
971 | "script file name (lang:script name, script name, or *)", | ||
972 | parse_scriptname), | ||
973 | OPT_STRING('g', "gen-script", &generate_script_lang, "lang", | ||
974 | "generate perf-script.xx script in specified language"), | ||
975 | OPT_STRING('i', "input", &input_name, "file", | ||
976 | "input file name"), | ||
977 | OPT_BOOLEAN('d', "debug-mode", &debug_mode, | ||
978 | "do various checks like samples ordering and lost events"), | ||
979 | OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, | ||
980 | "file", "vmlinux pathname"), | ||
981 | OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, | ||
982 | "file", "kallsyms pathname"), | ||
983 | OPT_BOOLEAN('G', "hide-call-graph", &no_callchain, | ||
984 | "When printing symbols do not display call chain"), | ||
985 | OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", | ||
986 | "Look for files with symbols relative to this directory"), | ||
987 | OPT_CALLBACK('f', "fields", NULL, "str", | ||
988 | "comma separated output fields prepend with 'type:'. Valid types: hw,sw,trace,raw. Fields: comm,tid,pid,time,cpu,event,trace,sym", | ||
989 | parse_output_fields), | ||
990 | |||
991 | OPT_END() | ||
992 | }; | ||
993 | |||
994 | static bool have_cmd(int argc, const char **argv) | ||
995 | { | ||
996 | char **__argv = malloc(sizeof(const char *) * argc); | ||
997 | |||
998 | if (!__argv) | ||
999 | die("malloc"); | ||
1000 | memcpy(__argv, argv, sizeof(const char *) * argc); | ||
1001 | argc = parse_options(argc, (const char **)__argv, record_options, | ||
1002 | NULL, PARSE_OPT_STOP_AT_NON_OPTION); | ||
1003 | free(__argv); | ||
1004 | |||
1005 | return argc != 0; | ||
1006 | } | ||
1007 | |||
1008 | int cmd_script(int argc, const char **argv, const char *prefix __used) | ||
1009 | { | ||
1010 | char *rec_script_path = NULL; | ||
1011 | char *rep_script_path = NULL; | ||
1012 | struct perf_session *session; | ||
1013 | char *script_path = NULL; | ||
1014 | const char **__argv; | ||
1015 | bool system_wide; | ||
1016 | int i, j, err; | ||
1017 | |||
1018 | setup_scripting(); | ||
1019 | |||
1020 | argc = parse_options(argc, argv, options, script_usage, | ||
1021 | PARSE_OPT_STOP_AT_NON_OPTION); | ||
1022 | |||
1023 | if (argc > 1 && !strncmp(argv[0], "rec", strlen("rec"))) { | ||
1024 | rec_script_path = get_script_path(argv[1], RECORD_SUFFIX); | ||
1025 | if (!rec_script_path) | ||
1026 | return cmd_record(argc, argv, NULL); | ||
1027 | } | ||
1028 | |||
1029 | if (argc > 1 && !strncmp(argv[0], "rep", strlen("rep"))) { | ||
1030 | rep_script_path = get_script_path(argv[1], REPORT_SUFFIX); | ||
1031 | if (!rep_script_path) { | ||
1032 | fprintf(stderr, | ||
1033 | "Please specify a valid report script" | ||
1034 | "(see 'perf script -l' for listing)\n"); | ||
1035 | return -1; | ||
1036 | } | ||
1037 | } | ||
1038 | |||
1039 | /* make sure PERF_EXEC_PATH is set for scripts */ | ||
1040 | perf_set_argv_exec_path(perf_exec_path()); | ||
1041 | |||
1042 | if (argc && !script_name && !rec_script_path && !rep_script_path) { | ||
1043 | int live_pipe[2]; | ||
1044 | int rep_args; | ||
1045 | pid_t pid; | ||
1046 | |||
1047 | rec_script_path = get_script_path(argv[0], RECORD_SUFFIX); | ||
1048 | rep_script_path = get_script_path(argv[0], REPORT_SUFFIX); | ||
1049 | |||
1050 | if (!rec_script_path && !rep_script_path) { | ||
1051 | fprintf(stderr, " Couldn't find script %s\n\n See perf" | ||
1052 | " script -l for available scripts.\n", argv[0]); | ||
1053 | usage_with_options(script_usage, options); | ||
1054 | } | ||
1055 | |||
1056 | if (is_top_script(argv[0])) { | ||
1057 | rep_args = argc - 1; | ||
1058 | } else { | ||
1059 | int rec_args; | ||
1060 | |||
1061 | rep_args = has_required_arg(rep_script_path); | ||
1062 | rec_args = (argc - 1) - rep_args; | ||
1063 | if (rec_args < 0) { | ||
1064 | fprintf(stderr, " %s script requires options." | ||
1065 | "\n\n See perf script -l for available " | ||
1066 | "scripts and options.\n", argv[0]); | ||
1067 | usage_with_options(script_usage, options); | ||
1068 | } | ||
1069 | } | ||
1070 | |||
1071 | if (pipe(live_pipe) < 0) { | ||
1072 | perror("failed to create pipe"); | ||
1073 | exit(-1); | ||
1074 | } | ||
1075 | |||
1076 | pid = fork(); | ||
1077 | if (pid < 0) { | ||
1078 | perror("failed to fork"); | ||
1079 | exit(-1); | ||
1080 | } | ||
1081 | |||
1082 | if (!pid) { | ||
1083 | system_wide = true; | ||
1084 | j = 0; | ||
1085 | |||
1086 | dup2(live_pipe[1], 1); | ||
1087 | close(live_pipe[0]); | ||
1088 | |||
1089 | if (!is_top_script(argv[0])) | ||
1090 | system_wide = !have_cmd(argc - rep_args, | ||
1091 | &argv[rep_args]); | ||
1092 | |||
1093 | __argv = malloc((argc + 6) * sizeof(const char *)); | ||
1094 | if (!__argv) | ||
1095 | die("malloc"); | ||
1096 | |||
1097 | __argv[j++] = "/bin/sh"; | ||
1098 | __argv[j++] = rec_script_path; | ||
1099 | if (system_wide) | ||
1100 | __argv[j++] = "-a"; | ||
1101 | __argv[j++] = "-q"; | ||
1102 | __argv[j++] = "-o"; | ||
1103 | __argv[j++] = "-"; | ||
1104 | for (i = rep_args + 1; i < argc; i++) | ||
1105 | __argv[j++] = argv[i]; | ||
1106 | __argv[j++] = NULL; | ||
1107 | |||
1108 | execvp("/bin/sh", (char **)__argv); | ||
1109 | free(__argv); | ||
1110 | exit(-1); | ||
1111 | } | ||
1112 | |||
1113 | dup2(live_pipe[0], 0); | ||
1114 | close(live_pipe[1]); | ||
1115 | |||
1116 | __argv = malloc((argc + 4) * sizeof(const char *)); | ||
1117 | if (!__argv) | ||
1118 | die("malloc"); | ||
1119 | j = 0; | ||
1120 | __argv[j++] = "/bin/sh"; | ||
1121 | __argv[j++] = rep_script_path; | ||
1122 | for (i = 1; i < rep_args + 1; i++) | ||
1123 | __argv[j++] = argv[i]; | ||
1124 | __argv[j++] = "-i"; | ||
1125 | __argv[j++] = "-"; | ||
1126 | __argv[j++] = NULL; | ||
1127 | |||
1128 | execvp("/bin/sh", (char **)__argv); | ||
1129 | free(__argv); | ||
1130 | exit(-1); | ||
1131 | } | ||
1132 | |||
1133 | if (rec_script_path) | ||
1134 | script_path = rec_script_path; | ||
1135 | if (rep_script_path) | ||
1136 | script_path = rep_script_path; | ||
1137 | |||
1138 | if (script_path) { | ||
1139 | system_wide = false; | ||
1140 | j = 0; | ||
1141 | |||
1142 | if (rec_script_path) | ||
1143 | system_wide = !have_cmd(argc - 1, &argv[1]); | ||
1144 | |||
1145 | __argv = malloc((argc + 2) * sizeof(const char *)); | ||
1146 | if (!__argv) | ||
1147 | die("malloc"); | ||
1148 | __argv[j++] = "/bin/sh"; | ||
1149 | __argv[j++] = script_path; | ||
1150 | if (system_wide) | ||
1151 | __argv[j++] = "-a"; | ||
1152 | for (i = 2; i < argc; i++) | ||
1153 | __argv[j++] = argv[i]; | ||
1154 | __argv[j++] = NULL; | ||
1155 | |||
1156 | execvp("/bin/sh", (char **)__argv); | ||
1157 | free(__argv); | ||
1158 | exit(-1); | ||
1159 | } | ||
1160 | |||
1161 | if (symbol__init() < 0) | ||
1162 | return -1; | ||
1163 | if (!script_name) | ||
1164 | setup_pager(); | ||
1165 | |||
1166 | session = perf_session__new(input_name, O_RDONLY, 0, false, &event_ops); | ||
1167 | if (session == NULL) | ||
1168 | return -ENOMEM; | ||
1169 | |||
1170 | if (!no_callchain) | ||
1171 | symbol_conf.use_callchain = true; | ||
1172 | else | ||
1173 | symbol_conf.use_callchain = false; | ||
1174 | |||
1175 | if (generate_script_lang) { | ||
1176 | struct stat perf_stat; | ||
1177 | int input; | ||
1178 | |||
1179 | if (output_set_by_user()) { | ||
1180 | fprintf(stderr, | ||
1181 | "custom fields not supported for generated scripts"); | ||
1182 | return -1; | ||
1183 | } | ||
1184 | |||
1185 | input = open(input_name, O_RDONLY); | ||
1186 | if (input < 0) { | ||
1187 | perror("failed to open file"); | ||
1188 | exit(-1); | ||
1189 | } | ||
1190 | |||
1191 | err = fstat(input, &perf_stat); | ||
1192 | if (err < 0) { | ||
1193 | perror("failed to stat file"); | ||
1194 | exit(-1); | ||
1195 | } | ||
1196 | |||
1197 | if (!perf_stat.st_size) { | ||
1198 | fprintf(stderr, "zero-sized file, nothing to do!\n"); | ||
1199 | exit(0); | ||
1200 | } | ||
1201 | |||
1202 | scripting_ops = script_spec__lookup(generate_script_lang); | ||
1203 | if (!scripting_ops) { | ||
1204 | fprintf(stderr, "invalid language specifier"); | ||
1205 | return -1; | ||
1206 | } | ||
1207 | |||
1208 | err = scripting_ops->generate_script("perf-script"); | ||
1209 | goto out; | ||
1210 | } | ||
1211 | |||
1212 | if (script_name) { | ||
1213 | err = scripting_ops->start_script(script_name, argc, argv); | ||
1214 | if (err) | ||
1215 | goto out; | ||
1216 | pr_debug("perf script started with script %s\n\n", script_name); | ||
1217 | } | ||
1218 | |||
1219 | |||
1220 | err = perf_session__check_output_opt(session); | ||
1221 | if (err < 0) | ||
1222 | goto out; | ||
1223 | |||
1224 | err = __cmd_script(session); | ||
1225 | |||
1226 | perf_session__delete(session); | ||
1227 | cleanup_scripting(); | ||
1228 | out: | ||
1229 | return err; | ||
1230 | } | ||